README.md
1# libnpmhook
2
3[](https://npm.im/libnpmhook)
4[](https://npm.im/libnpmhook)
5[](https://github.com/npm/cli/actions/workflows/ci-libnpmhook.yml)
6
7[`libnpmhook`](https://github.com/npm/libnpmhook) is a Node.js library for
8programmatically managing the npm registry's server-side hooks.
9
10For a more general introduction to managing hooks, see [the introductory blog
11post](https://blog.npmjs.org/post/145260155635/introducing-hooks-get-notifications-of-npm).
12
13## Table of Contents
14
15* [Example](#example)
16* [Install](#install)
17* [Contributing](#contributing)
18* [API](#api)
19 * [hook opts](#opts)
20 * [`add()`](#add)
21 * [`rm()`](#rm)
22 * [`ls()`](#ls)
23 * [`ls.stream()`](#ls-stream)
24 * [`update()`](#update)
25
26## Example
27
28```js
29const hooks = require('libnpmhook')
30
31console.log(await hooks.ls('mypkg', {token: 'deadbeef'}))
32// array of hook objects on `mypkg`.
33```
34
35## Install
36
37`$ npm install libnpmhook`
38
39### API
40
41#### <a name="opts"></a> `opts` for `libnpmhook` commands
42
43`libnpmhook` uses [`npm-registry-fetch`](https://npm.im/npm-registry-fetch).
44All options are passed through directly to that library, so please refer to [its
45own `opts`
46documentation](https://www.npmjs.com/package/npm-registry-fetch#fetch-options)
47for options that can be passed in.
48
49A couple of options of note for those in a hurry:
50
51* `opts.token` - can be passed in and will be used as the authentication token for the registry. For other ways to pass in auth details, see the n-r-f docs.
52* `opts.otp` - certain operations will require an OTP token to be passed in. If a `libnpmhook` command fails with `err.code === EOTP`, please retry the request with `{otp: <2fa token>}`
53
54#### <a name="add"></a> `> hooks.add(name, endpoint, secret, [opts]) -> Promise`
55
56`name` is the name of the package, org, or user/org scope to watch. The type is
57determined by the name syntax: `'@foo/bar'` and `'foo'` are treated as packages,
58`@foo` is treated as a scope, and `~user` is treated as an org name or scope.
59Each type will attach to different events.
60
61The `endpoint` should be a fully-qualified http URL for the endpoint the hook
62will send its payload to when it fires. `secret` is a shared secret that the
63hook will send to that endpoint to verify that it's actually coming from the
64registry hook.
65
66The returned Promise resolves to the full hook object that was created,
67including its generated `id`.
68
69See also: [`POST
70/v1/hooks/hook`](https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md#post-v1hookshook)
71
72##### Example
73
74```javascript
75await hooks.add('~zkat', 'https://example.com/api/added', 'supersekrit', {
76 token: 'myregistrytoken',
77 otp: '694207'
78})
79
80=>
81
82{ id: '16f7xoal',
83 username: 'zkat',
84 name: 'zkat',
85 endpoint: 'https://example.com/api/added',
86 secret: 'supersekrit',
87 type: 'owner',
88 created: '2018-08-21T20:05:25.125Z',
89 updated: '2018-08-21T20:05:25.125Z',
90 deleted: false,
91 delivered: false,
92 last_delivery: null,
93 response_code: 0,
94 status: 'active' }
95```
96
97#### <a name="find"></a> `> hooks.find(id, [opts]) -> Promise`
98
99Returns the hook identified by `id`.
100
101The returned Promise resolves to the full hook object that was found, or error
102with `err.code` of `'E404'` if it didn't exist.
103
104See also: [`GET
105/v1/hooks/hook/:id`](https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md#get-v1hookshookid)
106
107##### Example
108
109```javascript
110await hooks.find('16f7xoal', {token: 'myregistrytoken'})
111
112=>
113
114{ id: '16f7xoal',
115 username: 'zkat',
116 name: 'zkat',
117 endpoint: 'https://example.com/api/added',
118 secret: 'supersekrit',
119 type: 'owner',
120 created: '2018-08-21T20:05:25.125Z',
121 updated: '2018-08-21T20:05:25.125Z',
122 deleted: false,
123 delivered: false,
124 last_delivery: null,
125 response_code: 0,
126 status: 'active' }
127```
128
129#### <a name="rm"></a> `> hooks.rm(id, [opts]) -> Promise`
130
131Removes the hook identified by `id`.
132
133The returned Promise resolves to the full hook object that was removed, if it
134existed, or `null` if no such hook was there (instead of erroring).
135
136See also: [`DELETE
137/v1/hooks/hook/:id`](https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md#delete-v1hookshookid)
138
139##### Example
140
141```javascript
142await hooks.rm('16f7xoal', {
143 token: 'myregistrytoken',
144 otp: '694207'
145})
146
147=>
148
149{ id: '16f7xoal',
150 username: 'zkat',
151 name: 'zkat',
152 endpoint: 'https://example.com/api/added',
153 secret: 'supersekrit',
154 type: 'owner',
155 created: '2018-08-21T20:05:25.125Z',
156 updated: '2018-08-21T20:05:25.125Z',
157 deleted: true,
158 delivered: false,
159 last_delivery: null,
160 response_code: 0,
161 status: 'active' }
162
163// Repeat it...
164await hooks.rm('16f7xoal', {
165 token: 'myregistrytoken',
166 otp: '694207'
167})
168
169=> null
170```
171
172#### <a name="update"></a> `> hooks.update(id, endpoint, secret, [opts]) -> Promise`
173
174The `id` should be a hook ID from a previously-created hook.
175
176The `endpoint` should be a fully-qualified http URL for the endpoint the hook
177will send its payload to when it fires. `secret` is a shared secret that the
178hook will send to that endpoint to verify that it's actually coming from the
179registry hook.
180
181The returned Promise resolves to the full hook object that was updated, if it
182existed. Otherwise, it will error with an `'E404'` error code.
183
184See also: [`PUT
185/v1/hooks/hook/:id`](https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md#put-v1hookshookid)
186
187##### Example
188
189```javascript
190await hooks.update('16fxoal', 'https://example.com/api/other', 'newsekrit', {
191 token: 'myregistrytoken',
192 otp: '694207'
193})
194
195=>
196
197{ id: '16f7xoal',
198 username: 'zkat',
199 name: 'zkat',
200 endpoint: 'https://example.com/api/other',
201 secret: 'newsekrit',
202 type: 'owner',
203 created: '2018-08-21T20:05:25.125Z',
204 updated: '2018-08-21T20:14:41.964Z',
205 deleted: false,
206 delivered: false,
207 last_delivery: null,
208 response_code: 0,
209 status: 'active' }
210```
211
212#### <a name="ls"></a> `> hooks.ls([opts]) -> Promise`
213
214Resolves to an array of hook objects associated with the account you're
215authenticated as.
216
217Results can be further filtered with three values that can be passed in through
218`opts`:
219
220* `opts.package` - filter results by package name
221* `opts.limit` - maximum number of hooks to return
222* `opts.offset` - pagination offset for results (use with `opts.limit`)
223
224See also:
225 * [`hooks.ls.stream()`](#ls-stream)
226 * [`GET
227/v1/hooks`](https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md#get-v1hooks)
228
229##### Example
230
231```javascript
232await hooks.ls({token: 'myregistrytoken'})
233
234=>
235[
236 { id: '16f7xoal', ... },
237 { id: 'wnyf98a1', ... },
238 ...
239]
240```
241
242#### <a name="ls-stream"></a> `> hooks.ls.stream([opts]) -> Stream`
243
244Returns a stream of hook objects associated with the account you're
245authenticated as. The returned stream is a valid `Symbol.asyncIterator` on
246`node@>=10`.
247
248Results can be further filtered with three values that can be passed in through
249`opts`:
250
251* `opts.package` - filter results by package name
252* `opts.limit` - maximum number of hooks to return
253* `opts.offset` - pagination offset for results (use with `opts.limit`)
254
255See also:
256 * [`hooks.ls()`](#ls)
257 * [`GET
258/v1/hooks`](https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md#get-v1hooks)
259
260##### Example
261
262```javascript
263for await (let hook of hooks.ls.stream({token: 'myregistrytoken'})) {
264 console.log('found hook:', hook.id)
265}
266
267=>
268// outputs:
269// found hook: 16f7xoal
270// found hook: wnyf98a1
271```
272