Skip to content
This repository was archived by the owner on Jul 9, 2024. It is now read-only.

Commit 9b9364f

Browse files
committed
feat: wait for bulk registration success
1 parent 484cf81 commit 9b9364f

2 files changed

Lines changed: 110 additions & 54 deletions

File tree

api-verification/api-client.ts

Lines changed: 81 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as https from 'https'
22
import { URL } from 'url'
33
import { URLSearchParams } from 'url'
44
import * as jwt from 'jsonwebtoken'
5-
import { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http'
5+
import { IncomingHttpHeaders, IncomingMessage, OutgoingHttpHeaders } from 'http'
66

77
const token = (tokenKey: string, payload: Record<string, any>) =>
88
jwt.sign(payload, tokenKey, { algorithm: 'ES256' })
@@ -17,6 +17,9 @@ export const tokenAuthorization = ({
1717

1818
const DEFAULT_ENDPOINT = 'https://api.nrfcloud.com'
1919

20+
const ok = (res: IncomingMessage) =>
21+
(res?.statusCode ?? -1) >= 200 && (res?.statusCode ?? -1) < 300
22+
2023
export const apiClient = ({
2124
endpoint,
2225
authorizationToken,
@@ -29,6 +32,7 @@ export const apiClient = ({
2932
getBinary: typeof getBinary
3033
post: typeof post
3134
postBinary: typeof postBinary
35+
deleteJSON: typeof deleteJSON
3236
} => {
3337
const e = new URL(endpoint ?? DEFAULT_ENDPOINT)
3438
const post = async ({
@@ -73,7 +77,7 @@ export const apiClient = ({
7377
].join('\n'),
7478
)
7579

76-
if (res.statusCode !== 200)
80+
if (!ok(res))
7781
return reject(new Error(`Request failed: ${res.statusCode}`))
7882
resolve(JSON.parse(response.join('')))
7983
})
@@ -125,71 +129,83 @@ export const apiClient = ({
125129
].join('\n'),
126130
)
127131

128-
if (res.statusCode !== 200)
132+
if (!ok(res))
129133
return reject(new Error(`Request failed: ${res.statusCode}`))
130134
resolve(JSON.parse(response.join('')))
131135
})
132136
})
133137
req.on('error', reject)
134-
req.write(JSON.stringify(payload))
138+
req.write(payload)
135139
req.end()
136140
})
137141

138-
const executeGet = async ({
139-
resource,
140-
payload,
141-
headers,
142-
debugResponse,
143-
}: {
144-
resource: string
145-
payload: Record<string, any>
146-
headers?: OutgoingHttpHeaders
147-
debugResponse: (res: Buffer) => string[]
148-
}) =>
149-
new Promise<Buffer>((resolve, reject) => {
150-
const options = {
151-
hostname: e.hostname,
152-
port: 443,
153-
path: `/v1/${resource}?${new URLSearchParams(payload).toString()}`,
154-
headers: {
155-
Authorization: `Bearer ${authorizationToken}`,
156-
...headers,
157-
},
158-
}
142+
const executeMethod =
143+
(method: string) =>
144+
async ({
145+
resource,
146+
payload,
147+
headers,
148+
debugResponse,
149+
}: {
150+
resource: string
151+
payload?: Record<string, any>
152+
headers?: OutgoingHttpHeaders
153+
debugResponse: (res: Buffer) => string[]
154+
}) =>
155+
new Promise<Buffer>((resolve, reject) => {
156+
const options = {
157+
hostname: e.hostname,
158+
method,
159+
port: 443,
160+
path: `/v1/${resource}${
161+
payload !== undefined
162+
? `?${new URLSearchParams(payload).toString()}`
163+
: ''
164+
}`,
165+
headers: {
166+
Authorization: `Bearer ${authorizationToken}`,
167+
...headers,
168+
},
169+
}
159170

160-
const req = https.get(options, (res) => {
161-
const response: Buffer[] = []
171+
const req = https.get(options, (res) => {
172+
const response: Buffer[] = []
162173

163-
res.on('data', (d) => {
164-
response.push(d)
165-
})
174+
res.on('data', (d) => {
175+
response.push(d)
176+
})
166177

167-
res.on('end', () => {
168-
const data = Buffer.concat(response)
169-
console.debug(
170-
[
171-
`> GET https://${e.hostname}/v1/${resource}?${new URLSearchParams(
172-
payload,
173-
).toString()}`,
174-
...Object.entries(options.headers).map(
175-
([k, v]) => `> ${k}: ${v}`,
176-
),
177-
'',
178-
`< ${res.statusCode} ${res.statusMessage}`,
179-
...Object.entries(res.headers).map(([k, v]) => `< ${k}: ${v}`),
180-
'<',
181-
...debugResponse(data),
182-
].join('\n'),
183-
)
178+
res.on('end', () => {
179+
const data = Buffer.concat(response)
180+
console.debug(
181+
[
182+
`> ${method} https://${e.hostname}/v1/${resource}${
183+
payload !== undefined
184+
? `?${new URLSearchParams(payload).toString()}`
185+
: ''
186+
}`,
187+
...Object.entries(options.headers).map(
188+
([k, v]) => `> ${k}: ${v}`,
189+
),
190+
'',
191+
`< ${res.statusCode} ${res.statusMessage}`,
192+
...Object.entries(res.headers).map(([k, v]) => `< ${k}: ${v}`),
193+
'<',
194+
...debugResponse(data),
195+
].join('\n'),
196+
)
184197

185-
if ((res.statusCode ?? -1) > 399)
186-
return reject(new Error(`Request failed: ${res.statusCode}`))
187-
return resolve(data)
198+
if ((res.statusCode ?? -1) > 399)
199+
return reject(new Error(`Request failed: ${res.statusCode}`))
200+
return resolve(data)
201+
})
188202
})
203+
req.on('error', reject)
204+
req.end()
189205
})
190-
req.on('error', reject)
191-
req.end()
192-
})
206+
207+
const executeGet = executeMethod('GET')
208+
const executeDelete = executeMethod('DELETE')
193209

194210
const getJSON = async <Response extends Record<string, any>>(
195211
args: Pick<
@@ -202,6 +218,17 @@ export const apiClient = ({
202218
debugResponse: (res: Buffer) => [`< ${res.toString('utf-8')}`],
203219
}).then((res) => JSON.parse(res.toString('utf-8')))
204220

221+
const deleteJSON = async <Response extends Record<string, any>>(
222+
args: Pick<
223+
Parameters<typeof executeDelete>[0],
224+
Exclude<keyof Parameters<typeof executeDelete>[0], 'debugResponse'>
225+
>,
226+
): Promise<Response> =>
227+
executeDelete({
228+
...args,
229+
debugResponse: (res: Buffer) => [`< ${res.toString('utf-8')}`],
230+
}).then((res) => JSON.parse(res.toString('utf-8')))
231+
205232
const getBinary = async (
206233
args: Pick<
207234
Parameters<typeof executeGet>[0],
@@ -259,5 +286,6 @@ export const apiClient = ({
259286
getBinary,
260287
post,
261288
postBinary,
289+
deleteJSON,
262290
}
263291
}

api-verification/device-token-authentication.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('authenticate using device keys', () => {
1414
let privateKey: string
1515
let publicKey: string
1616
let deviceId: string
17+
let bulkOpsRequestId: string
1718

1819
beforeAll(async () => {
1920
// Generate a globally uniqe device ID
@@ -72,10 +73,37 @@ describe('authenticate using device keys', () => {
7273
})
7374

7475
it('should register a new device key', async () => {
75-
await apiKeyClient.postBinary({
76+
const { bulkOpsRequestId: rid } = await apiKeyClient.postBinary({
7677
resource: 'devices/public-keys',
7778
payload: `${deviceId},"${publicKey}"`,
7879
})
80+
bulkOpsRequestId = rid
81+
expect(bulkOpsRequestId).not.toBeUndefined()
82+
})
83+
84+
it('should process the request', async () => {
85+
const getStatus = async () =>
86+
apiKeyClient
87+
.getJSON({
88+
resource: `bulk-ops-requests/${bulkOpsRequestId}`,
89+
})
90+
.then(({ status }) => status)
91+
const status = await new Promise((resolve, reject) => {
92+
let t: NodeJS.Timeout | undefined = undefined
93+
const i = setInterval(async () => {
94+
const status = await getStatus()
95+
if (status !== 'PENDING') {
96+
clearInterval(i)
97+
if (t !== undefined) clearTimeout(t)
98+
resolve(status)
99+
}
100+
}, 1000)
101+
t = setTimeout(() => {
102+
clearInterval(i)
103+
reject(new Error(`Timeout`))
104+
}, 30000)
105+
})
106+
expect(status).toEqual('SUCCEEDED')
79107
})
80108

81109
it('should accept the device-key based JWT', async () => {

0 commit comments

Comments
 (0)