@logto/connector-github 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +203 -800
- package/lib/index.js.map +1 -0
- package/package.json +11 -16
- package/lib/constant.d.ts +0 -12
- package/lib/index.d.ts +0 -9
- package/lib/mock.d.ts +0 -4
- package/lib/types.d.ts +0 -86
package/lib/index.js
CHANGED
|
@@ -1,809 +1,212 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
onError?.(error);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// eslint-lint-disable-next-line @typescript-eslint/naming-convention
|
|
52
|
-
class HTTPError extends Error {
|
|
53
|
-
constructor(response, request, options) {
|
|
54
|
-
const code = (response.status || response.status === 0) ? response.status : '';
|
|
55
|
-
const title = response.statusText || '';
|
|
56
|
-
const status = `${code} ${title}`.trim();
|
|
57
|
-
const reason = status ? `status code ${status}` : 'an unknown error';
|
|
58
|
-
super(`Request failed with ${reason}`);
|
|
59
|
-
Object.defineProperty(this, "response", {
|
|
60
|
-
enumerable: true,
|
|
61
|
-
configurable: true,
|
|
62
|
-
writable: true,
|
|
63
|
-
value: void 0
|
|
64
|
-
});
|
|
65
|
-
Object.defineProperty(this, "request", {
|
|
66
|
-
enumerable: true,
|
|
67
|
-
configurable: true,
|
|
68
|
-
writable: true,
|
|
69
|
-
value: void 0
|
|
70
|
-
});
|
|
71
|
-
Object.defineProperty(this, "options", {
|
|
72
|
-
enumerable: true,
|
|
73
|
-
configurable: true,
|
|
74
|
-
writable: true,
|
|
75
|
-
value: void 0
|
|
76
|
-
});
|
|
77
|
-
this.name = 'HTTPError';
|
|
78
|
-
this.response = response;
|
|
79
|
-
this.request = request;
|
|
80
|
-
this.options = options;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
class TimeoutError extends Error {
|
|
85
|
-
constructor(request) {
|
|
86
|
-
super('Request timed out');
|
|
87
|
-
Object.defineProperty(this, "request", {
|
|
88
|
-
enumerable: true,
|
|
89
|
-
configurable: true,
|
|
90
|
-
writable: true,
|
|
91
|
-
value: void 0
|
|
92
|
-
});
|
|
93
|
-
this.name = 'TimeoutError';
|
|
94
|
-
this.request = request;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
99
|
-
const isObject = (value) => value !== null && typeof value === 'object';
|
|
100
|
-
|
|
101
|
-
const validateAndMerge = (...sources) => {
|
|
102
|
-
for (const source of sources) {
|
|
103
|
-
if ((!isObject(source) || Array.isArray(source)) && source !== undefined) {
|
|
104
|
-
throw new TypeError('The `options` argument must be an object');
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return deepMerge({}, ...sources);
|
|
108
|
-
};
|
|
109
|
-
const mergeHeaders = (source1 = {}, source2 = {}) => {
|
|
110
|
-
const result = new globalThis.Headers(source1);
|
|
111
|
-
const isHeadersInstance = source2 instanceof globalThis.Headers;
|
|
112
|
-
const source = new globalThis.Headers(source2);
|
|
113
|
-
for (const [key, value] of source.entries()) {
|
|
114
|
-
if ((isHeadersInstance && value === 'undefined') || value === undefined) {
|
|
115
|
-
result.delete(key);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
result.set(key, value);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return result;
|
|
122
|
-
};
|
|
123
|
-
// TODO: Make this strongly-typed (no `any`).
|
|
124
|
-
const deepMerge = (...sources) => {
|
|
125
|
-
let returnValue = {};
|
|
126
|
-
let headers = {};
|
|
127
|
-
for (const source of sources) {
|
|
128
|
-
if (Array.isArray(source)) {
|
|
129
|
-
if (!Array.isArray(returnValue)) {
|
|
130
|
-
returnValue = [];
|
|
131
|
-
}
|
|
132
|
-
returnValue = [...returnValue, ...source];
|
|
133
|
-
}
|
|
134
|
-
else if (isObject(source)) {
|
|
135
|
-
for (let [key, value] of Object.entries(source)) {
|
|
136
|
-
if (isObject(value) && key in returnValue) {
|
|
137
|
-
value = deepMerge(returnValue[key], value);
|
|
138
|
-
}
|
|
139
|
-
returnValue = { ...returnValue, [key]: value };
|
|
140
|
-
}
|
|
141
|
-
if (isObject(source.headers)) {
|
|
142
|
-
headers = mergeHeaders(headers, source.headers);
|
|
143
|
-
returnValue.headers = headers;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return returnValue;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const supportsRequestStreams = (() => {
|
|
151
|
-
let duplexAccessed = false;
|
|
152
|
-
let hasContentType = false;
|
|
153
|
-
const supportsReadableStream = typeof globalThis.ReadableStream === 'function';
|
|
154
|
-
const supportsRequest = typeof globalThis.Request === 'function';
|
|
155
|
-
if (supportsReadableStream && supportsRequest) {
|
|
156
|
-
hasContentType = new globalThis.Request('https://empty.invalid', {
|
|
157
|
-
body: new globalThis.ReadableStream(),
|
|
158
|
-
method: 'POST',
|
|
159
|
-
// @ts-expect-error - Types are outdated.
|
|
160
|
-
get duplex() {
|
|
161
|
-
duplexAccessed = true;
|
|
162
|
-
return 'half';
|
|
163
|
-
},
|
|
164
|
-
}).headers.has('Content-Type');
|
|
165
|
-
}
|
|
166
|
-
return duplexAccessed && !hasContentType;
|
|
167
|
-
})();
|
|
168
|
-
const supportsAbortController = typeof globalThis.AbortController === 'function';
|
|
169
|
-
const supportsResponseStreams = typeof globalThis.ReadableStream === 'function';
|
|
170
|
-
const supportsFormData = typeof globalThis.FormData === 'function';
|
|
171
|
-
const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete'];
|
|
172
|
-
const responseTypes = {
|
|
173
|
-
json: 'application/json',
|
|
174
|
-
text: 'text/*',
|
|
175
|
-
formData: 'multipart/form-data',
|
|
176
|
-
arrayBuffer: '*/*',
|
|
177
|
-
blob: '*/*',
|
|
178
|
-
};
|
|
179
|
-
// The maximum value of a 32bit int (see issue #117)
|
|
180
|
-
const maxSafeTimeout = 2_147_483_647;
|
|
181
|
-
const stop = Symbol('stop');
|
|
182
|
-
const kyOptionKeys = {
|
|
183
|
-
json: true,
|
|
184
|
-
parseJson: true,
|
|
185
|
-
searchParams: true,
|
|
186
|
-
prefixUrl: true,
|
|
187
|
-
retry: true,
|
|
188
|
-
timeout: true,
|
|
189
|
-
hooks: true,
|
|
190
|
-
throwHttpErrors: true,
|
|
191
|
-
onDownloadProgress: true,
|
|
192
|
-
fetch: true,
|
|
193
|
-
};
|
|
194
|
-
const requestOptionsRegistry = {
|
|
195
|
-
method: true,
|
|
196
|
-
headers: true,
|
|
197
|
-
body: true,
|
|
198
|
-
mode: true,
|
|
199
|
-
credentials: true,
|
|
200
|
-
cache: true,
|
|
201
|
-
redirect: true,
|
|
202
|
-
referrer: true,
|
|
203
|
-
referrerPolicy: true,
|
|
204
|
-
integrity: true,
|
|
205
|
-
keepalive: true,
|
|
206
|
-
signal: true,
|
|
207
|
-
window: true,
|
|
208
|
-
dispatcher: true,
|
|
209
|
-
duplex: true,
|
|
210
|
-
priority: true,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
|
|
214
|
-
const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace'];
|
|
215
|
-
const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
|
|
216
|
-
const retryAfterStatusCodes = [413, 429, 503];
|
|
217
|
-
const defaultRetryOptions = {
|
|
218
|
-
limit: 2,
|
|
219
|
-
methods: retryMethods,
|
|
220
|
-
statusCodes: retryStatusCodes,
|
|
221
|
-
afterStatusCodes: retryAfterStatusCodes,
|
|
222
|
-
maxRetryAfter: Number.POSITIVE_INFINITY,
|
|
223
|
-
backoffLimit: Number.POSITIVE_INFINITY,
|
|
224
|
-
delay: attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000,
|
|
225
|
-
};
|
|
226
|
-
const normalizeRetryOptions = (retry = {}) => {
|
|
227
|
-
if (typeof retry === 'number') {
|
|
228
|
-
return {
|
|
229
|
-
...defaultRetryOptions,
|
|
230
|
-
limit: retry,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
if (retry.methods && !Array.isArray(retry.methods)) {
|
|
234
|
-
throw new Error('retry.methods must be an array');
|
|
235
|
-
}
|
|
236
|
-
if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
|
|
237
|
-
throw new Error('retry.statusCodes must be an array');
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
...defaultRetryOptions,
|
|
241
|
-
...retry,
|
|
242
|
-
afterStatusCodes: retryAfterStatusCodes,
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// `Promise.race()` workaround (#91)
|
|
247
|
-
async function timeout(request, init, abortController, options) {
|
|
248
|
-
return new Promise((resolve, reject) => {
|
|
249
|
-
const timeoutId = setTimeout(() => {
|
|
250
|
-
if (abortController) {
|
|
251
|
-
abortController.abort();
|
|
252
|
-
}
|
|
253
|
-
reject(new TimeoutError(request));
|
|
254
|
-
}, options.timeout);
|
|
255
|
-
void options
|
|
256
|
-
.fetch(request, init)
|
|
257
|
-
.then(resolve)
|
|
258
|
-
.catch(reject)
|
|
259
|
-
.then(() => {
|
|
260
|
-
clearTimeout(timeoutId);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// https://github.com/sindresorhus/delay/tree/ab98ae8dfcb38e1593286c94d934e70d14a4e111
|
|
266
|
-
async function delay(ms, { signal }) {
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
if (signal) {
|
|
269
|
-
signal.throwIfAborted();
|
|
270
|
-
signal.addEventListener('abort', abortHandler, { once: true });
|
|
271
|
-
}
|
|
272
|
-
function abortHandler() {
|
|
273
|
-
clearTimeout(timeoutId);
|
|
274
|
-
reject(signal.reason);
|
|
275
|
-
}
|
|
276
|
-
const timeoutId = setTimeout(() => {
|
|
277
|
-
signal?.removeEventListener('abort', abortHandler);
|
|
278
|
-
resolve();
|
|
279
|
-
}, ms);
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const findUnknownOptions = (request, options) => {
|
|
284
|
-
const unknownOptions = {};
|
|
285
|
-
for (const key in options) {
|
|
286
|
-
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && !(key in request)) {
|
|
287
|
-
unknownOptions[key] = options[key];
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return unknownOptions;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
class Ky {
|
|
294
|
-
static create(input, options) {
|
|
295
|
-
const ky = new Ky(input, options);
|
|
296
|
-
const function_ = async () => {
|
|
297
|
-
if (typeof ky._options.timeout === 'number' && ky._options.timeout > maxSafeTimeout) {
|
|
298
|
-
throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
|
|
299
|
-
}
|
|
300
|
-
// Delay the fetch so that body method shortcuts can set the Accept header
|
|
301
|
-
await Promise.resolve();
|
|
302
|
-
let response = await ky._fetch();
|
|
303
|
-
for (const hook of ky._options.hooks.afterResponse) {
|
|
304
|
-
// eslint-disable-next-line no-await-in-loop
|
|
305
|
-
const modifiedResponse = await hook(ky.request, ky._options, ky._decorateResponse(response.clone()));
|
|
306
|
-
if (modifiedResponse instanceof globalThis.Response) {
|
|
307
|
-
response = modifiedResponse;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
ky._decorateResponse(response);
|
|
311
|
-
if (!response.ok && ky._options.throwHttpErrors) {
|
|
312
|
-
let error = new HTTPError(response, ky.request, ky._options);
|
|
313
|
-
for (const hook of ky._options.hooks.beforeError) {
|
|
314
|
-
// eslint-disable-next-line no-await-in-loop
|
|
315
|
-
error = await hook(error);
|
|
316
|
-
}
|
|
317
|
-
throw error;
|
|
318
|
-
}
|
|
319
|
-
// If `onDownloadProgress` is passed, it uses the stream API internally
|
|
320
|
-
/* istanbul ignore next */
|
|
321
|
-
if (ky._options.onDownloadProgress) {
|
|
322
|
-
if (typeof ky._options.onDownloadProgress !== 'function') {
|
|
323
|
-
throw new TypeError('The `onDownloadProgress` option must be a function');
|
|
324
|
-
}
|
|
325
|
-
if (!supportsResponseStreams) {
|
|
326
|
-
throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
|
|
327
|
-
}
|
|
328
|
-
return ky._stream(response.clone(), ky._options.onDownloadProgress);
|
|
329
|
-
}
|
|
330
|
-
return response;
|
|
331
|
-
};
|
|
332
|
-
const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
|
|
333
|
-
const result = (isRetriableMethod ? ky._retry(function_) : function_());
|
|
334
|
-
for (const [type, mimeType] of Object.entries(responseTypes)) {
|
|
335
|
-
result[type] = async () => {
|
|
336
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
337
|
-
ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);
|
|
338
|
-
const awaitedResult = await result;
|
|
339
|
-
const response = awaitedResult.clone();
|
|
340
|
-
if (type === 'json') {
|
|
341
|
-
if (response.status === 204) {
|
|
342
|
-
return '';
|
|
343
|
-
}
|
|
344
|
-
const arrayBuffer = await response.clone().arrayBuffer();
|
|
345
|
-
const responseSize = arrayBuffer.byteLength;
|
|
346
|
-
if (responseSize === 0) {
|
|
347
|
-
return '';
|
|
348
|
-
}
|
|
349
|
-
if (options.parseJson) {
|
|
350
|
-
return options.parseJson(await response.text());
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return response[type]();
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
return result;
|
|
357
|
-
}
|
|
358
|
-
// eslint-disable-next-line complexity
|
|
359
|
-
constructor(input, options = {}) {
|
|
360
|
-
Object.defineProperty(this, "request", {
|
|
361
|
-
enumerable: true,
|
|
362
|
-
configurable: true,
|
|
363
|
-
writable: true,
|
|
364
|
-
value: void 0
|
|
365
|
-
});
|
|
366
|
-
Object.defineProperty(this, "abortController", {
|
|
367
|
-
enumerable: true,
|
|
368
|
-
configurable: true,
|
|
369
|
-
writable: true,
|
|
370
|
-
value: void 0
|
|
371
|
-
});
|
|
372
|
-
Object.defineProperty(this, "_retryCount", {
|
|
373
|
-
enumerable: true,
|
|
374
|
-
configurable: true,
|
|
375
|
-
writable: true,
|
|
376
|
-
value: 0
|
|
377
|
-
});
|
|
378
|
-
Object.defineProperty(this, "_input", {
|
|
379
|
-
enumerable: true,
|
|
380
|
-
configurable: true,
|
|
381
|
-
writable: true,
|
|
382
|
-
value: void 0
|
|
383
|
-
});
|
|
384
|
-
Object.defineProperty(this, "_options", {
|
|
385
|
-
enumerable: true,
|
|
386
|
-
configurable: true,
|
|
387
|
-
writable: true,
|
|
388
|
-
value: void 0
|
|
389
|
-
});
|
|
390
|
-
this._input = input;
|
|
391
|
-
const credentials = this._input instanceof Request && 'credentials' in Request.prototype
|
|
392
|
-
? this._input.credentials
|
|
393
|
-
: undefined;
|
|
394
|
-
this._options = {
|
|
395
|
-
...(credentials && { credentials }), // For exactOptionalPropertyTypes
|
|
396
|
-
...options,
|
|
397
|
-
headers: mergeHeaders(this._input.headers, options.headers),
|
|
398
|
-
hooks: deepMerge({
|
|
399
|
-
beforeRequest: [],
|
|
400
|
-
beforeRetry: [],
|
|
401
|
-
beforeError: [],
|
|
402
|
-
afterResponse: [],
|
|
403
|
-
}, options.hooks),
|
|
404
|
-
method: normalizeRequestMethod(options.method ?? this._input.method),
|
|
405
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
406
|
-
prefixUrl: String(options.prefixUrl || ''),
|
|
407
|
-
retry: normalizeRetryOptions(options.retry),
|
|
408
|
-
throwHttpErrors: options.throwHttpErrors !== false,
|
|
409
|
-
timeout: options.timeout ?? 10_000,
|
|
410
|
-
fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
|
|
411
|
-
};
|
|
412
|
-
if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
|
|
413
|
-
throw new TypeError('`input` must be a string, URL, or Request');
|
|
414
|
-
}
|
|
415
|
-
if (this._options.prefixUrl && typeof this._input === 'string') {
|
|
416
|
-
if (this._input.startsWith('/')) {
|
|
417
|
-
throw new Error('`input` must not begin with a slash when using `prefixUrl`');
|
|
418
|
-
}
|
|
419
|
-
if (!this._options.prefixUrl.endsWith('/')) {
|
|
420
|
-
this._options.prefixUrl += '/';
|
|
421
|
-
}
|
|
422
|
-
this._input = this._options.prefixUrl + this._input;
|
|
423
|
-
}
|
|
424
|
-
if (supportsAbortController) {
|
|
425
|
-
this.abortController = new globalThis.AbortController();
|
|
426
|
-
if (this._options.signal) {
|
|
427
|
-
const originalSignal = this._options.signal;
|
|
428
|
-
this._options.signal.addEventListener('abort', () => {
|
|
429
|
-
this.abortController.abort(originalSignal.reason);
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
this._options.signal = this.abortController.signal;
|
|
433
|
-
}
|
|
434
|
-
if (supportsRequestStreams) {
|
|
435
|
-
// @ts-expect-error - Types are outdated.
|
|
436
|
-
this._options.duplex = 'half';
|
|
437
|
-
}
|
|
438
|
-
this.request = new globalThis.Request(this._input, this._options);
|
|
439
|
-
if (this._options.searchParams) {
|
|
440
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
441
|
-
const textSearchParams = typeof this._options.searchParams === 'string'
|
|
442
|
-
? this._options.searchParams.replace(/^\?/, '')
|
|
443
|
-
: new URLSearchParams(this._options.searchParams).toString();
|
|
444
|
-
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
445
|
-
const searchParams = '?' + textSearchParams;
|
|
446
|
-
const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
|
|
447
|
-
// To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
|
|
448
|
-
if (((supportsFormData && this._options.body instanceof globalThis.FormData)
|
|
449
|
-
|| this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) {
|
|
450
|
-
this.request.headers.delete('content-type');
|
|
451
|
-
}
|
|
452
|
-
// The spread of `this.request` is required as otherwise it misses the `duplex` option for some reason and throws.
|
|
453
|
-
this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
|
|
454
|
-
}
|
|
455
|
-
if (this._options.json !== undefined) {
|
|
456
|
-
this._options.body = JSON.stringify(this._options.json);
|
|
457
|
-
this.request.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json');
|
|
458
|
-
this.request = new globalThis.Request(this.request, { body: this._options.body });
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
_calculateRetryDelay(error) {
|
|
462
|
-
this._retryCount++;
|
|
463
|
-
if (this._retryCount <= this._options.retry.limit && !(error instanceof TimeoutError)) {
|
|
464
|
-
if (error instanceof HTTPError) {
|
|
465
|
-
if (!this._options.retry.statusCodes.includes(error.response.status)) {
|
|
466
|
-
return 0;
|
|
467
|
-
}
|
|
468
|
-
const retryAfter = error.response.headers.get('Retry-After');
|
|
469
|
-
if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
|
|
470
|
-
let after = Number(retryAfter);
|
|
471
|
-
if (Number.isNaN(after)) {
|
|
472
|
-
after = Date.parse(retryAfter) - Date.now();
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
475
|
-
after *= 1000;
|
|
476
|
-
}
|
|
477
|
-
if (this._options.retry.maxRetryAfter !== undefined && after > this._options.retry.maxRetryAfter) {
|
|
478
|
-
return 0;
|
|
479
|
-
}
|
|
480
|
-
return after;
|
|
481
|
-
}
|
|
482
|
-
if (error.response.status === 413) {
|
|
483
|
-
return 0;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const retryDelay = this._options.retry.delay(this._retryCount);
|
|
487
|
-
return Math.min(this._options.retry.backoffLimit, retryDelay);
|
|
488
|
-
}
|
|
489
|
-
return 0;
|
|
490
|
-
}
|
|
491
|
-
_decorateResponse(response) {
|
|
492
|
-
if (this._options.parseJson) {
|
|
493
|
-
response.json = async () => this._options.parseJson(await response.text());
|
|
494
|
-
}
|
|
495
|
-
return response;
|
|
496
|
-
}
|
|
497
|
-
async _retry(function_) {
|
|
498
|
-
try {
|
|
499
|
-
return await function_();
|
|
500
|
-
}
|
|
501
|
-
catch (error) {
|
|
502
|
-
const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
|
|
503
|
-
if (ms !== 0 && this._retryCount > 0) {
|
|
504
|
-
await delay(ms, { signal: this._options.signal });
|
|
505
|
-
for (const hook of this._options.hooks.beforeRetry) {
|
|
506
|
-
// eslint-disable-next-line no-await-in-loop
|
|
507
|
-
const hookResult = await hook({
|
|
508
|
-
request: this.request,
|
|
509
|
-
options: this._options,
|
|
510
|
-
error: error,
|
|
511
|
-
retryCount: this._retryCount,
|
|
512
|
-
});
|
|
513
|
-
// If `stop` is returned from the hook, the retry process is stopped
|
|
514
|
-
if (hookResult === stop) {
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return this._retry(function_);
|
|
519
|
-
}
|
|
520
|
-
throw error;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
async _fetch() {
|
|
524
|
-
for (const hook of this._options.hooks.beforeRequest) {
|
|
525
|
-
// eslint-disable-next-line no-await-in-loop
|
|
526
|
-
const result = await hook(this.request, this._options);
|
|
527
|
-
if (result instanceof Request) {
|
|
528
|
-
this.request = result;
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
531
|
-
if (result instanceof Response) {
|
|
532
|
-
return result;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
const nonRequestOptions = findUnknownOptions(this.request, this._options);
|
|
536
|
-
if (this._options.timeout === false) {
|
|
537
|
-
return this._options.fetch(this.request.clone(), nonRequestOptions);
|
|
538
|
-
}
|
|
539
|
-
return timeout(this.request.clone(), nonRequestOptions, this.abortController, this._options);
|
|
540
|
-
}
|
|
541
|
-
/* istanbul ignore next */
|
|
542
|
-
_stream(response, onDownloadProgress) {
|
|
543
|
-
const totalBytes = Number(response.headers.get('content-length')) || 0;
|
|
544
|
-
let transferredBytes = 0;
|
|
545
|
-
if (response.status === 204) {
|
|
546
|
-
if (onDownloadProgress) {
|
|
547
|
-
onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
|
|
548
|
-
}
|
|
549
|
-
return new globalThis.Response(null, {
|
|
550
|
-
status: response.status,
|
|
551
|
-
statusText: response.statusText,
|
|
552
|
-
headers: response.headers,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
return new globalThis.Response(new globalThis.ReadableStream({
|
|
556
|
-
async start(controller) {
|
|
557
|
-
const reader = response.body.getReader();
|
|
558
|
-
if (onDownloadProgress) {
|
|
559
|
-
onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
|
|
560
|
-
}
|
|
561
|
-
async function read() {
|
|
562
|
-
const { done, value } = await reader.read();
|
|
563
|
-
if (done) {
|
|
564
|
-
controller.close();
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
if (onDownloadProgress) {
|
|
568
|
-
transferredBytes += value.byteLength;
|
|
569
|
-
const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
|
|
570
|
-
onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
|
|
571
|
-
}
|
|
572
|
-
controller.enqueue(value);
|
|
573
|
-
await read();
|
|
574
|
-
}
|
|
575
|
-
await read();
|
|
576
|
-
},
|
|
577
|
-
}), {
|
|
578
|
-
status: response.status,
|
|
579
|
-
statusText: response.statusText,
|
|
580
|
-
headers: response.headers,
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/*! MIT License © Sindre Sorhus */
|
|
586
|
-
const createInstance = (defaults) => {
|
|
587
|
-
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
588
|
-
const ky = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
|
|
589
|
-
for (const method of requestMethods) {
|
|
590
|
-
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
591
|
-
ky[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
|
|
592
|
-
}
|
|
593
|
-
ky.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
|
|
594
|
-
ky.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults));
|
|
595
|
-
ky.stop = stop;
|
|
596
|
-
return ky;
|
|
597
|
-
};
|
|
598
|
-
const ky = createInstance();
|
|
599
|
-
|
|
600
|
-
const authorizationEndpoint = 'https://github.com/login/oauth/authorize';
|
|
601
|
-
/**
|
|
602
|
-
* `read:user` read user profile data; `user:email` read user email addresses (including private email addresses).
|
|
603
|
-
* Ref: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
|
|
604
|
-
*/
|
|
605
|
-
const scope = 'read:user user:email';
|
|
606
|
-
const accessTokenEndpoint = 'https://github.com/login/oauth/access_token';
|
|
607
|
-
const userInfoEndpoint = 'https://api.github.com/user';
|
|
608
|
-
// Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user
|
|
609
|
-
const userEmailsEndpoint = 'https://api.github.com/user/emails';
|
|
610
|
-
const defaultMetadata = {
|
|
611
|
-
id: 'github-universal',
|
|
612
|
-
target: 'github',
|
|
613
|
-
platform: ConnectorPlatform.Universal,
|
|
614
|
-
name: {
|
|
615
|
-
en: 'GitHub',
|
|
616
|
-
'zh-CN': 'GitHub',
|
|
617
|
-
'tr-TR': 'GitHub',
|
|
618
|
-
ko: 'GitHub',
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { assert, conditional, trySafe } from "@silverhand/essentials";
|
|
3
|
+
import {
|
|
4
|
+
ConnectorError,
|
|
5
|
+
ConnectorErrorCodes,
|
|
6
|
+
validateConfig,
|
|
7
|
+
ConnectorType,
|
|
8
|
+
jsonGuard
|
|
9
|
+
} from "@logto/connector-kit";
|
|
10
|
+
import ky, { HTTPError } from "ky";
|
|
11
|
+
|
|
12
|
+
// src/constant.ts
|
|
13
|
+
import { ConnectorPlatform, ConnectorConfigFormItemType } from "@logto/connector-kit";
|
|
14
|
+
var authorizationEndpoint = "https://github.com/login/oauth/authorize";
|
|
15
|
+
var scope = "read:user user:email";
|
|
16
|
+
var accessTokenEndpoint = "https://github.com/login/oauth/access_token";
|
|
17
|
+
var userInfoEndpoint = "https://api.github.com/user";
|
|
18
|
+
var userEmailsEndpoint = "https://api.github.com/user/emails";
|
|
19
|
+
var defaultMetadata = {
|
|
20
|
+
id: "github-universal",
|
|
21
|
+
target: "github",
|
|
22
|
+
platform: ConnectorPlatform.Universal,
|
|
23
|
+
name: {
|
|
24
|
+
en: "GitHub",
|
|
25
|
+
"zh-CN": "GitHub",
|
|
26
|
+
"tr-TR": "GitHub",
|
|
27
|
+
ko: "GitHub"
|
|
28
|
+
},
|
|
29
|
+
logo: "./logo.svg",
|
|
30
|
+
logoDark: "./logo-dark.svg",
|
|
31
|
+
description: {
|
|
32
|
+
en: "GitHub is an online community for software development and version control.",
|
|
33
|
+
"zh-CN": "GitHub \u662F\u6781\u53D7\u6B22\u8FCE\u7684\u4EE3\u7801\u6258\u7BA1\u4ED3\u5E93\u3002",
|
|
34
|
+
"tr-TR": "GitHub, yaz\u0131l\u0131m geli\u015Ftirme ve s\xFCr\xFCm kontrol\xFC i\xE7in \xE7evrimi\xE7i bir topluluktur.",
|
|
35
|
+
ko: "GitHub\uB294 \uC18C\uD504\uD2B8\uC6E8\uC5B4 \uAC1C\uBC1C\uACFC \uBC84\uC804 \uAD00\uB9AC\uB97C \uC704\uD55C \uC628\uB77C\uC778 \uCEE4\uBBA4\uB2C8\uD2F0\uC785\uB2C8\uB2E4."
|
|
36
|
+
},
|
|
37
|
+
readme: "./README.md",
|
|
38
|
+
formItems: [
|
|
39
|
+
{
|
|
40
|
+
key: "clientId",
|
|
41
|
+
type: ConnectorConfigFormItemType.Text,
|
|
42
|
+
label: "Client ID",
|
|
43
|
+
required: true,
|
|
44
|
+
placeholder: "<client-id>"
|
|
619
45
|
},
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
ko: 'GitHub는 소프트웨어 개발과 버전 관리를 위한 온라인 커뮤니티입니다.',
|
|
46
|
+
{
|
|
47
|
+
key: "clientSecret",
|
|
48
|
+
type: ConnectorConfigFormItemType.Text,
|
|
49
|
+
label: "Client Secret",
|
|
50
|
+
required: true,
|
|
51
|
+
placeholder: "<client-secret>"
|
|
627
52
|
},
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
type: ConnectorConfigFormItemType.Text,
|
|
647
|
-
label: 'Scope',
|
|
648
|
-
required: false,
|
|
649
|
-
placeholder: '<scope>',
|
|
650
|
-
description: "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.",
|
|
651
|
-
},
|
|
652
|
-
],
|
|
653
|
-
};
|
|
654
|
-
const defaultTimeout = 5000;
|
|
655
|
-
|
|
656
|
-
const githubConfigGuard = z.object({
|
|
657
|
-
clientId: z.string(),
|
|
658
|
-
clientSecret: z.string(),
|
|
659
|
-
scope: z.string().optional(),
|
|
53
|
+
{
|
|
54
|
+
key: "scope",
|
|
55
|
+
type: ConnectorConfigFormItemType.Text,
|
|
56
|
+
label: "Scope",
|
|
57
|
+
required: false,
|
|
58
|
+
placeholder: "<scope>",
|
|
59
|
+
description: "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank."
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
var defaultTimeout = 5e3;
|
|
64
|
+
|
|
65
|
+
// src/types.ts
|
|
66
|
+
import { z } from "zod";
|
|
67
|
+
var githubConfigGuard = z.object({
|
|
68
|
+
clientId: z.string(),
|
|
69
|
+
clientSecret: z.string(),
|
|
70
|
+
scope: z.string().optional()
|
|
660
71
|
});
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
email: z.string(),
|
|
667
|
-
primary: z.boolean(),
|
|
668
|
-
verified: z.boolean(),
|
|
669
|
-
visibility: z.string().nullable(),
|
|
72
|
+
var emailAddressGuard = z.object({
|
|
73
|
+
email: z.string(),
|
|
74
|
+
primary: z.boolean(),
|
|
75
|
+
verified: z.boolean(),
|
|
76
|
+
visibility: z.string().nullable()
|
|
670
77
|
});
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
78
|
+
var accessTokenResponseGuard = z.object({
|
|
79
|
+
access_token: z.string(),
|
|
80
|
+
scope: z.string(),
|
|
81
|
+
token_type: z.string()
|
|
675
82
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
83
|
+
var userInfoResponseGuard = z.object({
|
|
84
|
+
id: z.number(),
|
|
85
|
+
avatar_url: z.string().optional().nullable(),
|
|
86
|
+
email: z.string().optional().nullable(),
|
|
87
|
+
name: z.string().optional().nullable()
|
|
681
88
|
});
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
89
|
+
var authorizationCallbackErrorGuard = z.object({
|
|
90
|
+
error: z.string(),
|
|
91
|
+
error_description: z.string(),
|
|
92
|
+
error_uri: z.string()
|
|
686
93
|
});
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const userEmailsResult = emailAddressGuard.array().safeParse(userEmails);
|
|
769
|
-
if (!userInfoResult.success) {
|
|
770
|
-
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
|
|
771
|
-
}
|
|
772
|
-
if (!userEmailsResult.success) {
|
|
773
|
-
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userEmailsResult.error);
|
|
774
|
-
}
|
|
775
|
-
const { id, avatar_url: avatar, email: publicEmail, name } = userInfoResult.data;
|
|
776
|
-
return {
|
|
777
|
-
id: String(id),
|
|
778
|
-
avatar: conditional(avatar),
|
|
779
|
-
email: conditional(publicEmail ??
|
|
780
|
-
userEmailsResult.data.find(({ verified, primary }) => verified && primary)?.email),
|
|
781
|
-
name: conditional(name),
|
|
782
|
-
rawData: jsonGuard.parse({
|
|
783
|
-
userInfo,
|
|
784
|
-
userEmails,
|
|
785
|
-
}),
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
catch (error) {
|
|
789
|
-
if (error instanceof HTTPError) {
|
|
790
|
-
const { status, body: rawBody } = error.response;
|
|
791
|
-
if (status === 401) {
|
|
792
|
-
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
|
|
793
|
-
}
|
|
794
|
-
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
|
|
795
|
-
}
|
|
796
|
-
throw error;
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
const createGithubConnector = async ({ getConfig }) => {
|
|
94
|
+
var authResponseGuard = z.object({ code: z.string() });
|
|
95
|
+
|
|
96
|
+
// src/index.ts
|
|
97
|
+
var getAuthorizationUri = (getConfig) => async ({ state, redirectUri }) => {
|
|
98
|
+
const config = await getConfig(defaultMetadata.id);
|
|
99
|
+
validateConfig(config, githubConfigGuard);
|
|
100
|
+
const queryParameters = new URLSearchParams({
|
|
101
|
+
client_id: config.clientId,
|
|
102
|
+
redirect_uri: redirectUri,
|
|
103
|
+
state,
|
|
104
|
+
scope: config.scope ?? scope
|
|
105
|
+
});
|
|
106
|
+
return `${authorizationEndpoint}?${queryParameters.toString()}`;
|
|
107
|
+
};
|
|
108
|
+
var authorizationCallbackHandler = async (parameterObject) => {
|
|
109
|
+
const result = authResponseGuard.safeParse(parameterObject);
|
|
110
|
+
if (result.success) {
|
|
111
|
+
return result.data;
|
|
112
|
+
}
|
|
113
|
+
const parsedError = authorizationCallbackErrorGuard.safeParse(parameterObject);
|
|
114
|
+
if (!parsedError.success) {
|
|
115
|
+
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
|
|
116
|
+
}
|
|
117
|
+
const { error, error_description, error_uri } = parsedError.data;
|
|
118
|
+
if (error === "access_denied") {
|
|
119
|
+
throw new ConnectorError(ConnectorErrorCodes.AuthorizationFailed, error_description);
|
|
120
|
+
}
|
|
121
|
+
throw new ConnectorError(ConnectorErrorCodes.General, {
|
|
122
|
+
error,
|
|
123
|
+
errorDescription: error_description,
|
|
124
|
+
error_uri
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
var getAccessToken = async (config, codeObject) => {
|
|
128
|
+
const { code } = codeObject;
|
|
129
|
+
const { clientId: client_id, clientSecret: client_secret } = config;
|
|
130
|
+
const httpResponse = await ky.post(accessTokenEndpoint, {
|
|
131
|
+
body: new URLSearchParams({
|
|
132
|
+
client_id,
|
|
133
|
+
client_secret,
|
|
134
|
+
code
|
|
135
|
+
}),
|
|
136
|
+
timeout: defaultTimeout
|
|
137
|
+
}).json();
|
|
138
|
+
const result = accessTokenResponseGuard.safeParse(httpResponse);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
|
|
141
|
+
}
|
|
142
|
+
const { access_token: accessToken } = result.data;
|
|
143
|
+
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
|
144
|
+
return { accessToken };
|
|
145
|
+
};
|
|
146
|
+
var getUserInfo = (getConfig) => async (data) => {
|
|
147
|
+
const { code } = await authorizationCallbackHandler(data);
|
|
148
|
+
const config = await getConfig(defaultMetadata.id);
|
|
149
|
+
validateConfig(config, githubConfigGuard);
|
|
150
|
+
const { accessToken } = await getAccessToken(config, { code });
|
|
151
|
+
const authedApi = ky.create({
|
|
152
|
+
timeout: defaultTimeout,
|
|
153
|
+
hooks: {
|
|
154
|
+
beforeRequest: [
|
|
155
|
+
(request) => {
|
|
156
|
+
request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
const [userInfo, userEmails = []] = await Promise.all([
|
|
163
|
+
authedApi.get(userInfoEndpoint).json(),
|
|
164
|
+
trySafe(authedApi.get(userEmailsEndpoint).json())
|
|
165
|
+
]);
|
|
166
|
+
const userInfoResult = userInfoResponseGuard.safeParse(userInfo);
|
|
167
|
+
const userEmailsResult = emailAddressGuard.array().safeParse(userEmails);
|
|
168
|
+
if (!userInfoResult.success) {
|
|
169
|
+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
|
|
170
|
+
}
|
|
171
|
+
if (!userEmailsResult.success) {
|
|
172
|
+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userEmailsResult.error);
|
|
173
|
+
}
|
|
174
|
+
const { id, avatar_url: avatar, email: publicEmail, name } = userInfoResult.data;
|
|
800
175
|
return {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
176
|
+
id: String(id),
|
|
177
|
+
avatar: conditional(avatar),
|
|
178
|
+
email: conditional(
|
|
179
|
+
publicEmail ?? userEmailsResult.data.find(({ verified, primary }) => verified && primary)?.email
|
|
180
|
+
),
|
|
181
|
+
name: conditional(name),
|
|
182
|
+
rawData: jsonGuard.parse({
|
|
183
|
+
userInfo,
|
|
184
|
+
userEmails
|
|
185
|
+
})
|
|
806
186
|
};
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error instanceof HTTPError) {
|
|
189
|
+
const { status, body: rawBody } = error.response;
|
|
190
|
+
if (status === 401) {
|
|
191
|
+
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
|
|
192
|
+
}
|
|
193
|
+
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var createGithubConnector = async ({ getConfig }) => {
|
|
199
|
+
return {
|
|
200
|
+
metadata: defaultMetadata,
|
|
201
|
+
type: ConnectorType.Social,
|
|
202
|
+
configGuard: githubConfigGuard,
|
|
203
|
+
getAuthorizationUri: getAuthorizationUri(getConfig),
|
|
204
|
+
getUserInfo: getUserInfo(getConfig)
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
var src_default = createGithubConnector;
|
|
208
|
+
export {
|
|
209
|
+
src_default as default,
|
|
210
|
+
getAccessToken
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constant.ts","../src/types.ts"],"sourcesContent":["import { assert, conditional, trySafe } from '@silverhand/essentials';\n\nimport {\n ConnectorError,\n ConnectorErrorCodes,\n validateConfig,\n ConnectorType,\n jsonGuard,\n} from '@logto/connector-kit';\nimport type {\n GetAuthorizationUri,\n GetUserInfo,\n SocialConnector,\n CreateConnector,\n GetConnectorConfig,\n} from '@logto/connector-kit';\nimport ky, { HTTPError } from 'ky';\n\nimport {\n authorizationEndpoint,\n accessTokenEndpoint,\n scope as defaultScope,\n userInfoEndpoint,\n userEmailsEndpoint,\n defaultMetadata,\n defaultTimeout,\n} from './constant.js';\nimport type { GithubConfig } from './types.js';\nimport {\n authorizationCallbackErrorGuard,\n githubConfigGuard,\n emailAddressGuard,\n accessTokenResponseGuard,\n userInfoResponseGuard,\n authResponseGuard,\n} from './types.js';\n\nconst getAuthorizationUri =\n (getConfig: GetConnectorConfig): GetAuthorizationUri =>\n async ({ state, redirectUri }) => {\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, githubConfigGuard);\n const queryParameters = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: redirectUri,\n state,\n scope: config.scope ?? defaultScope,\n });\n\n return `${authorizationEndpoint}?${queryParameters.toString()}`;\n };\n\nconst authorizationCallbackHandler = async (parameterObject: unknown) => {\n const result = authResponseGuard.safeParse(parameterObject);\n\n if (result.success) {\n return result.data;\n }\n\n const parsedError = authorizationCallbackErrorGuard.safeParse(parameterObject);\n\n if (!parsedError.success) {\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));\n }\n\n const { error, error_description, error_uri } = parsedError.data;\n\n if (error === 'access_denied') {\n throw new ConnectorError(ConnectorErrorCodes.AuthorizationFailed, error_description);\n }\n\n throw new ConnectorError(ConnectorErrorCodes.General, {\n error,\n errorDescription: error_description,\n error_uri,\n });\n};\n\nexport const getAccessToken = async (config: GithubConfig, codeObject: { code: string }) => {\n const { code } = codeObject;\n const { clientId: client_id, clientSecret: client_secret } = config;\n\n const httpResponse = await ky\n .post(accessTokenEndpoint, {\n body: new URLSearchParams({\n client_id,\n client_secret,\n code,\n }),\n timeout: defaultTimeout,\n })\n .json();\n\n const result = accessTokenResponseGuard.safeParse(httpResponse);\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);\n }\n\n const { access_token: accessToken } = result.data;\n\n assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));\n\n return { accessToken };\n};\n\nconst getUserInfo =\n (getConfig: GetConnectorConfig): GetUserInfo =>\n async (data) => {\n const { code } = await authorizationCallbackHandler(data);\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, githubConfigGuard);\n const { accessToken } = await getAccessToken(config, { code });\n\n const authedApi = ky.create({\n timeout: defaultTimeout,\n hooks: {\n beforeRequest: [\n (request) => {\n request.headers.set('Authorization', `Bearer ${accessToken}`);\n },\n ],\n },\n });\n\n try {\n /**\n * If user(s) is using GitHub Apps (instead of OAuth Apps), they can customize\n * \"Account permissions\" and restrict the \"email addresses\" visibility, and GitHub\n * hence throws error instead of returning an empty array.\n *\n * We try catch the error and return an empty array instead.\n */\n const [userInfo, userEmails = []] = await Promise.all([\n authedApi.get(userInfoEndpoint).json(),\n trySafe(authedApi.get(userEmailsEndpoint).json()),\n ]);\n\n const userInfoResult = userInfoResponseGuard.safeParse(userInfo);\n const userEmailsResult = emailAddressGuard.array().safeParse(userEmails);\n\n if (!userInfoResult.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);\n }\n\n if (!userEmailsResult.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userEmailsResult.error);\n }\n\n const { id, avatar_url: avatar, email: publicEmail, name } = userInfoResult.data;\n\n return {\n id: String(id),\n avatar: conditional(avatar),\n email: conditional(\n publicEmail ??\n userEmailsResult.data.find(({ verified, primary }) => verified && primary)?.email\n ),\n name: conditional(name),\n rawData: jsonGuard.parse({\n userInfo,\n userEmails,\n }),\n };\n } catch (error: unknown) {\n if (error instanceof HTTPError) {\n const { status, body: rawBody } = error.response;\n\n if (status === 401) {\n throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);\n }\n\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));\n }\n\n throw error;\n }\n };\n\nconst createGithubConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {\n return {\n metadata: defaultMetadata,\n type: ConnectorType.Social,\n configGuard: githubConfigGuard,\n getAuthorizationUri: getAuthorizationUri(getConfig),\n getUserInfo: getUserInfo(getConfig),\n };\n};\n\nexport default createGithubConnector;\n","import type { ConnectorMetadata } from '@logto/connector-kit';\nimport { ConnectorPlatform, ConnectorConfigFormItemType } from '@logto/connector-kit';\n\nexport const authorizationEndpoint = 'https://github.com/login/oauth/authorize';\n/**\n * `read:user` read user profile data; `user:email` read user email addresses (including private email addresses).\n * Ref: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps\n */\nexport const scope = 'read:user user:email';\nexport const accessTokenEndpoint = 'https://github.com/login/oauth/access_token';\nexport const userInfoEndpoint = 'https://api.github.com/user';\n// Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user\nexport const userEmailsEndpoint = 'https://api.github.com/user/emails';\n\nexport const defaultMetadata: ConnectorMetadata = {\n id: 'github-universal',\n target: 'github',\n platform: ConnectorPlatform.Universal,\n name: {\n en: 'GitHub',\n 'zh-CN': 'GitHub',\n 'tr-TR': 'GitHub',\n ko: 'GitHub',\n },\n logo: './logo.svg',\n logoDark: './logo-dark.svg',\n description: {\n en: 'GitHub is an online community for software development and version control.',\n 'zh-CN': 'GitHub 是极受欢迎的代码托管仓库。',\n 'tr-TR': 'GitHub, yazılım geliştirme ve sürüm kontrolü için çevrimiçi bir topluluktur.',\n ko: 'GitHub는 소프트웨어 개발과 버전 관리를 위한 온라인 커뮤니티입니다.',\n },\n readme: './README.md',\n formItems: [\n {\n key: 'clientId',\n type: ConnectorConfigFormItemType.Text,\n label: 'Client ID',\n required: true,\n placeholder: '<client-id>',\n },\n {\n key: 'clientSecret',\n type: ConnectorConfigFormItemType.Text,\n label: 'Client Secret',\n required: true,\n placeholder: '<client-secret>',\n },\n {\n key: 'scope',\n type: ConnectorConfigFormItemType.Text,\n label: 'Scope',\n required: false,\n placeholder: '<scope>',\n description:\n \"The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.\",\n },\n ],\n};\n\nexport const defaultTimeout = 5000;\n","import { z } from 'zod';\n\nexport const githubConfigGuard = z.object({\n clientId: z.string(),\n clientSecret: z.string(),\n scope: z.string().optional(),\n});\n\nexport type GithubConfig = z.infer<typeof githubConfigGuard>;\n\n/**\n * This guard is used to validate the response from the GitHub API when requesting the user's email addresses.\n * Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user\n */\nexport const emailAddressGuard = z.object({\n email: z.string(),\n primary: z.boolean(),\n verified: z.boolean(),\n visibility: z.string().nullable(),\n});\n\nexport const accessTokenResponseGuard = z.object({\n access_token: z.string(),\n scope: z.string(),\n token_type: z.string(),\n});\n\nexport type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;\n\nexport const userInfoResponseGuard = z.object({\n id: z.number(),\n avatar_url: z.string().optional().nullable(),\n email: z.string().optional().nullable(),\n name: z.string().optional().nullable(),\n});\n\nexport type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;\n\nexport const authorizationCallbackErrorGuard = z.object({\n error: z.string(),\n error_description: z.string(),\n error_uri: z.string(),\n});\n\nexport const authResponseGuard = z.object({ code: z.string() });\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/connector-github",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Github web connector implementation.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"dependencies": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"@silverhand/essentials": "^2.9.1",
|
|
9
9
|
"ky": "^1.2.3",
|
|
10
10
|
"query-string": "^9.0.0",
|
|
11
|
-
"snakecase-keys": "^8.0.
|
|
12
|
-
"zod": "^3.
|
|
11
|
+
"snakecase-keys": "^8.0.1",
|
|
12
|
+
"zod": "^3.23.8"
|
|
13
13
|
},
|
|
14
14
|
"main": "./lib/index.js",
|
|
15
15
|
"module": "./lib/index.js",
|
|
@@ -42,30 +42,25 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@rollup/plugin-commonjs": "^26.0.0",
|
|
46
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
47
|
-
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
48
|
-
"@rollup/plugin-typescript": "^11.1.6",
|
|
49
45
|
"@silverhand/eslint-config": "6.0.1",
|
|
50
46
|
"@silverhand/ts-config": "6.0.0",
|
|
51
47
|
"@types/node": "^20.11.20",
|
|
52
48
|
"@types/supertest": "^6.0.2",
|
|
53
|
-
"@vitest/coverage-v8": "^
|
|
49
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
54
50
|
"eslint": "^8.56.0",
|
|
55
51
|
"lint-staged": "^15.0.2",
|
|
56
|
-
"nock": "14.0.0-beta.
|
|
52
|
+
"nock": "14.0.0-beta.9",
|
|
57
53
|
"prettier": "^3.0.0",
|
|
58
|
-
"rollup": "^4.12.0",
|
|
59
|
-
"rollup-plugin-output-size": "^1.3.0",
|
|
60
54
|
"supertest": "^7.0.0",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
55
|
+
"tsup": "^8.1.0",
|
|
56
|
+
"typescript": "^5.5.3",
|
|
57
|
+
"vitest": "^2.0.0"
|
|
63
58
|
},
|
|
64
59
|
"scripts": {
|
|
65
60
|
"precommit": "lint-staged",
|
|
66
|
-
"
|
|
67
|
-
"build": "
|
|
68
|
-
"dev": "
|
|
61
|
+
"check": "tsc --noEmit",
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"dev": "tsup --watch",
|
|
69
64
|
"lint": "eslint --ext .ts src",
|
|
70
65
|
"lint:report": "pnpm lint --format json --output-file report.json",
|
|
71
66
|
"test": "vitest src",
|
package/lib/constant.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ConnectorMetadata } from '@logto/connector-kit';
|
|
2
|
-
export declare const authorizationEndpoint = "https://github.com/login/oauth/authorize";
|
|
3
|
-
/**
|
|
4
|
-
* `read:user` read user profile data; `user:email` read user email addresses (including private email addresses).
|
|
5
|
-
* Ref: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
|
|
6
|
-
*/
|
|
7
|
-
export declare const scope = "read:user user:email";
|
|
8
|
-
export declare const accessTokenEndpoint = "https://github.com/login/oauth/access_token";
|
|
9
|
-
export declare const userInfoEndpoint = "https://api.github.com/user";
|
|
10
|
-
export declare const userEmailsEndpoint = "https://api.github.com/user/emails";
|
|
11
|
-
export declare const defaultMetadata: ConnectorMetadata;
|
|
12
|
-
export declare const defaultTimeout = 5000;
|
package/lib/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { SocialConnector, CreateConnector } from '@logto/connector-kit';
|
|
2
|
-
import type { GithubConfig } from './types.js';
|
|
3
|
-
export declare const getAccessToken: (config: GithubConfig, codeObject: {
|
|
4
|
-
code: string;
|
|
5
|
-
}) => Promise<{
|
|
6
|
-
accessToken: string;
|
|
7
|
-
}>;
|
|
8
|
-
declare const createGithubConnector: CreateConnector<SocialConnector>;
|
|
9
|
-
export default createGithubConnector;
|
package/lib/mock.d.ts
DELETED
package/lib/types.d.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const githubConfigGuard: z.ZodObject<{
|
|
3
|
-
clientId: z.ZodString;
|
|
4
|
-
clientSecret: z.ZodString;
|
|
5
|
-
scope: z.ZodOptional<z.ZodString>;
|
|
6
|
-
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
clientId: string;
|
|
8
|
-
clientSecret: string;
|
|
9
|
-
scope?: string | undefined;
|
|
10
|
-
}, {
|
|
11
|
-
clientId: string;
|
|
12
|
-
clientSecret: string;
|
|
13
|
-
scope?: string | undefined;
|
|
14
|
-
}>;
|
|
15
|
-
export type GithubConfig = z.infer<typeof githubConfigGuard>;
|
|
16
|
-
/**
|
|
17
|
-
* This guard is used to validate the response from the GitHub API when requesting the user's email addresses.
|
|
18
|
-
* Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user
|
|
19
|
-
*/
|
|
20
|
-
export declare const emailAddressGuard: z.ZodObject<{
|
|
21
|
-
email: z.ZodString;
|
|
22
|
-
primary: z.ZodBoolean;
|
|
23
|
-
verified: z.ZodBoolean;
|
|
24
|
-
visibility: z.ZodNullable<z.ZodString>;
|
|
25
|
-
}, "strip", z.ZodTypeAny, {
|
|
26
|
-
email: string;
|
|
27
|
-
primary: boolean;
|
|
28
|
-
verified: boolean;
|
|
29
|
-
visibility: string | null;
|
|
30
|
-
}, {
|
|
31
|
-
email: string;
|
|
32
|
-
primary: boolean;
|
|
33
|
-
verified: boolean;
|
|
34
|
-
visibility: string | null;
|
|
35
|
-
}>;
|
|
36
|
-
export declare const accessTokenResponseGuard: z.ZodObject<{
|
|
37
|
-
access_token: z.ZodString;
|
|
38
|
-
scope: z.ZodString;
|
|
39
|
-
token_type: z.ZodString;
|
|
40
|
-
}, "strip", z.ZodTypeAny, {
|
|
41
|
-
scope: string;
|
|
42
|
-
access_token: string;
|
|
43
|
-
token_type: string;
|
|
44
|
-
}, {
|
|
45
|
-
scope: string;
|
|
46
|
-
access_token: string;
|
|
47
|
-
token_type: string;
|
|
48
|
-
}>;
|
|
49
|
-
export type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;
|
|
50
|
-
export declare const userInfoResponseGuard: z.ZodObject<{
|
|
51
|
-
id: z.ZodNumber;
|
|
52
|
-
avatar_url: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
53
|
-
email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
54
|
-
name: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
55
|
-
}, "strip", z.ZodTypeAny, {
|
|
56
|
-
id: number;
|
|
57
|
-
avatar_url?: string | null | undefined;
|
|
58
|
-
email?: string | null | undefined;
|
|
59
|
-
name?: string | null | undefined;
|
|
60
|
-
}, {
|
|
61
|
-
id: number;
|
|
62
|
-
avatar_url?: string | null | undefined;
|
|
63
|
-
email?: string | null | undefined;
|
|
64
|
-
name?: string | null | undefined;
|
|
65
|
-
}>;
|
|
66
|
-
export type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;
|
|
67
|
-
export declare const authorizationCallbackErrorGuard: z.ZodObject<{
|
|
68
|
-
error: z.ZodString;
|
|
69
|
-
error_description: z.ZodString;
|
|
70
|
-
error_uri: z.ZodString;
|
|
71
|
-
}, "strip", z.ZodTypeAny, {
|
|
72
|
-
error: string;
|
|
73
|
-
error_description: string;
|
|
74
|
-
error_uri: string;
|
|
75
|
-
}, {
|
|
76
|
-
error: string;
|
|
77
|
-
error_description: string;
|
|
78
|
-
error_uri: string;
|
|
79
|
-
}>;
|
|
80
|
-
export declare const authResponseGuard: z.ZodObject<{
|
|
81
|
-
code: z.ZodString;
|
|
82
|
-
}, "strip", z.ZodTypeAny, {
|
|
83
|
-
code: string;
|
|
84
|
-
}, {
|
|
85
|
-
code: string;
|
|
86
|
-
}>;
|