@anmiles/google-api-wrapper 9.0.0 → 9.1.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/.eslintrc.js +3 -0
- package/CHANGELOG.md +6 -0
- package/dist/lib/secrets.js +21 -8
- package/dist/lib/secrets.js.map +1 -1
- package/package.json +3 -1
- package/src/lib/__tests__/secrets.test.ts +116 -114
- package/src/lib/secrets.ts +24 -9
package/.eslintrc.js
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [9.1.0](../../tags/v9.1.0) - 2023-05-26
|
|
9
|
+
### Changed
|
|
10
|
+
- Concurrent servers on the same port between different applications
|
|
11
|
+
- Use `event-emitter` to mock subscriptions on server/response
|
|
12
|
+
- Get rid of timeouts and promise races in tests
|
|
13
|
+
|
|
8
14
|
## [9.0.0](../../tags/v9.0.0) - 2023-05-15
|
|
9
15
|
### Changed
|
|
10
16
|
- Update `@anmiles/logger` with breaking change (removing timestamps for colored logs)
|
package/dist/lib/secrets.js
CHANGED
|
@@ -13,10 +13,12 @@ require("@anmiles/prototypes");
|
|
|
13
13
|
const paths_1 = require("./paths");
|
|
14
14
|
const secrets_1 = __importDefault(require("./secrets"));
|
|
15
15
|
exports.default = { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
16
|
+
const port = 6006;
|
|
17
|
+
const host = `localhost:${port}`;
|
|
18
|
+
const startURI = `http://${host}/`;
|
|
19
|
+
const callbackURI = `http://${host}/oauthcallback`;
|
|
19
20
|
const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
|
|
21
|
+
const serverRetryInterval = 1000;
|
|
20
22
|
function getScopes() {
|
|
21
23
|
const scopesFile = (0, paths_1.getScopesFile)();
|
|
22
24
|
const scopes = fs_1.default.getJSON(scopesFile, () => {
|
|
@@ -70,7 +72,9 @@ async function createCredentials(profile, auth, options, prompt) {
|
|
|
70
72
|
prompt,
|
|
71
73
|
scope,
|
|
72
74
|
});
|
|
73
|
-
const server = http_1.default.createServer(
|
|
75
|
+
const server = http_1.default.createServer();
|
|
76
|
+
(0, server_destroy_1.default)(server);
|
|
77
|
+
server.on('request', async (request, response) => {
|
|
74
78
|
if (!request.url) {
|
|
75
79
|
response.end('');
|
|
76
80
|
return;
|
|
@@ -87,10 +91,19 @@ async function createCredentials(profile, auth, options, prompt) {
|
|
|
87
91
|
const { tokens } = await auth.getToken(code);
|
|
88
92
|
resolve(tokens);
|
|
89
93
|
});
|
|
90
|
-
(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
server.on('error', (error) => {
|
|
95
|
+
if (error.code === 'EADDRINUSE') {
|
|
96
|
+
setTimeout(() => server.listen(port), serverRetryInterval);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
server.once('listening', () => {
|
|
103
|
+
(0, logger_1.warn)('Please check your browser for further actions');
|
|
104
|
+
(0, open_1.default)(startURI);
|
|
105
|
+
});
|
|
106
|
+
server.listen(port);
|
|
94
107
|
});
|
|
95
108
|
}
|
|
96
109
|
function formatMessage(message) {
|
package/dist/lib/secrets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AACxB,oEAA2C;AAC3C,gDAAwB;AAExB,4CAAuC;AAEvC,+BAA6B;AAC7B,mCAA4E;AAE5E,wDAAgC;AAGhC,kBAAe,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC;AAEhJ,MAAM,
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anmiles/google-api-wrapper",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "Provides quick interface for getting google API data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"google",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@anmiles/eslint-config": "^1.0.6",
|
|
42
|
+
"@types/event-emitter": "^0.3.3",
|
|
42
43
|
"@types/jest": "^29.5.1",
|
|
43
44
|
"@types/server-destroy": "^1.0.1",
|
|
44
45
|
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"eslint-plugin-align-assignments": "^1.1.2",
|
|
48
49
|
"eslint-plugin-import": "^2.27.5",
|
|
49
50
|
"eslint-plugin-jest": "^27.2.1",
|
|
51
|
+
"event-emitter": "^0.3.5",
|
|
50
52
|
"jest": "^29.5.0",
|
|
51
53
|
"nyc": "^15.1.0",
|
|
52
54
|
"rimraf": "^5.0.0",
|
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import open from 'open';
|
|
5
5
|
import type GoogleApis from 'googleapis';
|
|
6
6
|
import logger from '@anmiles/logger';
|
|
7
|
+
import emitter from 'event-emitter';
|
|
7
8
|
import paths from '../paths';
|
|
8
9
|
import type { Secrets } from '../../types';
|
|
9
10
|
import '@anmiles/prototypes';
|
|
@@ -22,18 +23,21 @@ jest.mock<typeof secrets>('../secrets', () => ({
|
|
|
22
23
|
}));
|
|
23
24
|
|
|
24
25
|
jest.mock<Partial<typeof http>>('http', () => ({
|
|
25
|
-
createServer : jest.fn().mockImplementation((
|
|
26
|
-
serverCallback = callback;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
on,
|
|
30
|
-
listen,
|
|
31
|
-
close,
|
|
32
|
-
destroy,
|
|
33
|
-
};
|
|
34
|
-
}),
|
|
26
|
+
createServer : jest.fn().mockImplementation(() => server),
|
|
35
27
|
}));
|
|
36
28
|
|
|
29
|
+
let server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
30
|
+
let response: http.ServerResponse;
|
|
31
|
+
|
|
32
|
+
async function makeRequest(url: string | undefined) {
|
|
33
|
+
server.emit('request', {
|
|
34
|
+
url,
|
|
35
|
+
headers : {
|
|
36
|
+
host,
|
|
37
|
+
},
|
|
38
|
+
} as http.IncomingMessage, response);
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
jest.mock<Partial<typeof fs>>('fs', () => ({
|
|
38
42
|
existsSync : jest.fn().mockImplementation(() => exists),
|
|
39
43
|
}));
|
|
@@ -43,7 +47,7 @@ jest.mock<Partial<typeof path>>('path', () => ({
|
|
|
43
47
|
}));
|
|
44
48
|
|
|
45
49
|
jest.mock('open', () => jest.fn().mockImplementation((url: string) => {
|
|
46
|
-
|
|
50
|
+
makeRequest(url.replace('http://localhost:6006', ''));
|
|
47
51
|
}));
|
|
48
52
|
|
|
49
53
|
jest.mock<Partial<typeof logger>>('@anmiles/logger', () => ({
|
|
@@ -56,6 +60,12 @@ jest.mock<Partial<typeof paths>>('../paths', () => ({
|
|
|
56
60
|
getCredentialsFile : jest.fn().mockImplementation(() => credentialsFile),
|
|
57
61
|
}));
|
|
58
62
|
|
|
63
|
+
jest.useFakeTimers();
|
|
64
|
+
|
|
65
|
+
const port = 6006;
|
|
66
|
+
const host = `localhost:${port}`;
|
|
67
|
+
const callbackURI = `http://${host}/oauthcallback`;
|
|
68
|
+
|
|
59
69
|
const profile = 'username1';
|
|
60
70
|
const scopesFile = 'scopes.json';
|
|
61
71
|
const secretsFile = 'secrets/username1.json';
|
|
@@ -79,7 +89,7 @@ const secretsJSON: Secrets = {
|
|
|
79
89
|
token_uri : 'https://oauth2.googleapis.com/token',
|
|
80
90
|
auth_provider_x509_cert_url : 'https://www.googleapis.com/oauth2/v1/certs',
|
|
81
91
|
client_secret : 'client_secret',
|
|
82
|
-
redirect_uris : [
|
|
92
|
+
redirect_uris : [ callbackURI ],
|
|
83
93
|
/* eslint-enable camelcase */
|
|
84
94
|
},
|
|
85
95
|
};
|
|
@@ -98,46 +108,6 @@ const auth = {
|
|
|
98
108
|
getToken : jest.fn().mockResolvedValue({ tokens : credentialsJSON }),
|
|
99
109
|
} as unknown as GoogleApis.Common.OAuth2Client;
|
|
100
110
|
|
|
101
|
-
const response = {
|
|
102
|
-
end : jest.fn(),
|
|
103
|
-
} as unknown as http.ServerResponse;
|
|
104
|
-
|
|
105
|
-
let serverCallback: (
|
|
106
|
-
request: http.IncomingMessage,
|
|
107
|
-
response: http.ServerResponse
|
|
108
|
-
) => Promise<typeof credentialsJSON>;
|
|
109
|
-
|
|
110
|
-
function willOpen(url: string | undefined, timeout?: number) {
|
|
111
|
-
setTimeout(async () => {
|
|
112
|
-
await serverCallback({
|
|
113
|
-
url,
|
|
114
|
-
headers : {
|
|
115
|
-
host : 'localhost:6006',
|
|
116
|
-
},
|
|
117
|
-
} as http.IncomingMessage, response);
|
|
118
|
-
}, timeout || 0);
|
|
119
|
-
}
|
|
120
|
-
let closedTime: number;
|
|
121
|
-
|
|
122
|
-
const on = jest.fn().mockImplementation((event: string, listener: (...args: any[]) => void) => {
|
|
123
|
-
if (event === 'connection') {
|
|
124
|
-
// always simulate opening several connections once connections are meant to be listened
|
|
125
|
-
connections.forEach((connection) => listener(connection));
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const listen = jest.fn();
|
|
130
|
-
const close = jest.fn().mockImplementation(() => {
|
|
131
|
-
closedTime = new Date().getTime();
|
|
132
|
-
});
|
|
133
|
-
const destroy = jest.fn();
|
|
134
|
-
|
|
135
|
-
const connections = [
|
|
136
|
-
{ remoteAddress : 'server', remotePort : '1001', on : jest.fn(), destroy : jest.fn() },
|
|
137
|
-
{ remoteAddress : 'server', remotePort : '1002', on : jest.fn(), destroy : jest.fn() },
|
|
138
|
-
{ remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
|
|
139
|
-
];
|
|
140
|
-
|
|
141
111
|
let exists: boolean;
|
|
142
112
|
|
|
143
113
|
let getJSONSpy: jest.SpyInstance;
|
|
@@ -360,10 +330,38 @@ describe('src/lib/secrets', () => {
|
|
|
360
330
|
describe('createCredentials', () => {
|
|
361
331
|
const tokenUrl = `/request.url?code=${code}`;
|
|
362
332
|
|
|
363
|
-
|
|
364
|
-
|
|
333
|
+
const connections = [
|
|
334
|
+
{ remoteAddress : 'server', remotePort : '1001', on : jest.fn(), destroy : jest.fn() },
|
|
335
|
+
{ remoteAddress : 'server', remotePort : '1002', on : jest.fn(), destroy : jest.fn() },
|
|
336
|
+
{ remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
let endSpy: jest.SpyInstance;
|
|
340
|
+
|
|
341
|
+
beforeEach(() => {
|
|
342
|
+
server = emitter({
|
|
343
|
+
listen : jest.fn().mockImplementation(() => {
|
|
344
|
+
// always simulate opening several connections once connections are meant to be listened
|
|
345
|
+
connections.forEach((connection) => server.emit('connection', connection));
|
|
346
|
+
}),
|
|
347
|
+
close : jest.fn(),
|
|
348
|
+
destroy : jest.fn(),
|
|
349
|
+
}) as typeof server;
|
|
350
|
+
|
|
351
|
+
response = emitter({
|
|
352
|
+
end : jest.fn(),
|
|
353
|
+
}) as typeof response;
|
|
354
|
+
|
|
355
|
+
endSpy = jest.spyOn(response, 'end');
|
|
356
|
+
});
|
|
365
357
|
|
|
366
|
-
|
|
358
|
+
afterAll(() => {
|
|
359
|
+
endSpy.mockRestore();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should generate authUrl', async () => {
|
|
363
|
+
original.createCredentials(profile, auth);
|
|
364
|
+
await Promise.resolve();
|
|
367
365
|
|
|
368
366
|
expect(auth.generateAuthUrl).toHaveBeenCalledWith({
|
|
369
367
|
// eslint-disable-next-line camelcase
|
|
@@ -377,9 +375,8 @@ describe('src/lib/secrets', () => {
|
|
|
377
375
|
});
|
|
378
376
|
|
|
379
377
|
it('should generate authUrl and require consent if explicitly asked', async () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
await original.createCredentials(profile, auth, { temporary : true }, 'consent');
|
|
378
|
+
original.createCredentials(profile, auth, { temporary : true }, 'consent');
|
|
379
|
+
await Promise.resolve();
|
|
383
380
|
|
|
384
381
|
expect(auth.generateAuthUrl).toHaveBeenCalledWith({
|
|
385
382
|
// eslint-disable-next-line camelcase
|
|
@@ -393,9 +390,8 @@ describe('src/lib/secrets', () => {
|
|
|
393
390
|
});
|
|
394
391
|
|
|
395
392
|
it('should generate authUrl with custom scopes', async () => {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
await original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
|
|
393
|
+
original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
|
|
394
|
+
await Promise.resolve();
|
|
399
395
|
|
|
400
396
|
expect(auth.generateAuthUrl).toHaveBeenCalledWith({
|
|
401
397
|
// eslint-disable-next-line camelcase
|
|
@@ -406,39 +402,45 @@ describe('src/lib/secrets', () => {
|
|
|
406
402
|
});
|
|
407
403
|
|
|
408
404
|
it('should create server on 6006 port', async () => {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
await original.createCredentials(profile, auth);
|
|
405
|
+
original.createCredentials(profile, auth);
|
|
406
|
+
await Promise.resolve();
|
|
412
407
|
|
|
413
408
|
expect(http.createServer).toHaveBeenCalled();
|
|
414
|
-
expect(listen).toHaveBeenCalledWith(6006);
|
|
409
|
+
expect(server.listen).toHaveBeenCalledWith(6006);
|
|
415
410
|
});
|
|
416
411
|
|
|
417
|
-
it('should open browser page and warn about it', async () => {
|
|
418
|
-
|
|
412
|
+
it('should open browser page and warn about it once listening', async () => {
|
|
413
|
+
original.createCredentials(profile, auth);
|
|
414
|
+
await Promise.resolve();
|
|
419
415
|
|
|
420
|
-
|
|
416
|
+
server.emit('listening');
|
|
421
417
|
|
|
422
418
|
expect(open).toHaveBeenCalledWith('http://localhost:6006/');
|
|
423
419
|
expect(logger.warn).toHaveBeenCalledWith('Please check your browser for further actions');
|
|
424
420
|
});
|
|
425
421
|
|
|
426
|
-
it('should
|
|
427
|
-
|
|
428
|
-
|
|
422
|
+
it('should not open browser page and warn about it until listening', async () => {
|
|
423
|
+
original.createCredentials(profile, auth);
|
|
424
|
+
await Promise.resolve();
|
|
429
425
|
|
|
430
|
-
|
|
426
|
+
expect(open).not.toHaveBeenCalled();
|
|
427
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should show nothing on the browser page if request.url is empty', async () => {
|
|
431
|
+
original.createCredentials(profile, auth);
|
|
432
|
+
makeRequest('');
|
|
433
|
+
await Promise.resolve();
|
|
431
434
|
|
|
432
|
-
expect(
|
|
435
|
+
expect(endSpy).toHaveBeenCalledWith('');
|
|
433
436
|
});
|
|
434
437
|
|
|
435
438
|
it('should show opening instructions if opened the home page', async () => {
|
|
436
|
-
|
|
437
|
-
|
|
439
|
+
original.createCredentials(profile, auth);
|
|
440
|
+
makeRequest('/');
|
|
441
|
+
await Promise.resolve();
|
|
438
442
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
expect(response.end).toHaveBeenCalledWith(`\
|
|
443
|
+
expect(endSpy).toHaveBeenCalledWith(`\
|
|
442
444
|
<div style="width: 100%;height: 100%;display: flex;align-items: start;justify-content: center">\n\
|
|
443
445
|
<div style="padding: 0 1em;border: 1px solid black;font-family: Arial, sans-serif;margin: 1em;">\n\
|
|
444
446
|
<p>Please open <a href="${authUrl}">auth page</a> using <strong>${profile}</strong> google profile</p>\n\
|
|
@@ -449,11 +451,11 @@ describe('src/lib/secrets', () => {
|
|
|
449
451
|
});
|
|
450
452
|
|
|
451
453
|
it('should ask to close webpage', async () => {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
await
|
|
454
|
+
original.createCredentials(profile, auth);
|
|
455
|
+
makeRequest(tokenUrl);
|
|
456
|
+
await Promise.resolve();
|
|
455
457
|
|
|
456
|
-
expect(
|
|
458
|
+
expect(endSpy).toHaveBeenCalledWith('\
|
|
457
459
|
<div style="width: 100%;height: 100%;display: flex;align-items: start;justify-content: center">\n\
|
|
458
460
|
<div style="padding: 0 1em;border: 1px solid black;font-family: Arial, sans-serif;margin: 1em;">\n\
|
|
459
461
|
<p>Please close this page and return to application</p>\n\
|
|
@@ -462,53 +464,53 @@ describe('src/lib/secrets', () => {
|
|
|
462
464
|
});
|
|
463
465
|
|
|
464
466
|
it('should close server and destroy all connections if request.url is truthy', async () => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
await
|
|
467
|
+
original.createCredentials(profile, auth);
|
|
468
|
+
makeRequest(tokenUrl);
|
|
469
|
+
await Promise.resolve();
|
|
468
470
|
|
|
469
|
-
expect(close).toHaveBeenCalled();
|
|
471
|
+
expect(server.close).toHaveBeenCalled();
|
|
470
472
|
|
|
471
473
|
connections.forEach((connection) => expect(connection.destroy).toHaveBeenCalled());
|
|
472
474
|
});
|
|
473
475
|
|
|
474
|
-
it('should
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
476
|
+
it('should close server and resolve if request.url is truthy', async () => {
|
|
477
|
+
const promise = original.createCredentials(profile, auth);
|
|
478
|
+
makeRequest(tokenUrl);
|
|
479
|
+
const result = await Promise.resolve(promise);
|
|
480
|
+
expect(result).toEqual(credentialsJSON);
|
|
481
|
+
expect(server.close).toHaveBeenCalledTimes(1);
|
|
482
|
+
});
|
|
481
483
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
+
it('should not close server if request.url is falsy', async () => {
|
|
485
|
+
original.createCredentials(profile, auth);
|
|
486
|
+
makeRequest(undefined);
|
|
487
|
+
await Promise.resolve();
|
|
484
488
|
|
|
485
|
-
expect(close).
|
|
486
|
-
expect(closedTime - before).toBeGreaterThanOrEqual(requestTime - 1);
|
|
487
|
-
expect(after - before).toBeGreaterThanOrEqual(requestTime - 1);
|
|
488
|
-
expect(result).toEqual(credentialsJSON);
|
|
489
|
+
expect(server.close).not.toHaveBeenCalled();
|
|
489
490
|
});
|
|
490
491
|
|
|
491
|
-
it('should
|
|
492
|
-
const
|
|
493
|
-
const requestTime = 200;
|
|
492
|
+
it('should re-throw a server error if error is not EADDRINUSE', () => {
|
|
493
|
+
const error = { code : 'RANDOM', message : 'random error' } as NodeJS.ErrnoException;
|
|
494
494
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
495
|
+
original.createCredentials(profile, auth);
|
|
496
|
+
expect(() => server.emit('error', error)).toThrow(error.message);
|
|
497
|
+
});
|
|
498
498
|
|
|
499
|
-
|
|
500
|
-
const
|
|
499
|
+
it('should not re-throw a server error and try to listen again in 1000 seconds if error is EADDRINUSE', () => {
|
|
500
|
+
const error = { code : 'EADDRINUSE' } as NodeJS.ErrnoException;
|
|
501
501
|
|
|
502
|
-
|
|
503
|
-
expect(
|
|
504
|
-
expect(
|
|
505
|
-
expect(
|
|
502
|
+
original.createCredentials(profile, auth);
|
|
503
|
+
expect(server.listen).toHaveBeenCalledTimes(1);
|
|
504
|
+
expect(() => server.emit('error', error)).not.toThrow();
|
|
505
|
+
expect(server.listen).toHaveBeenCalledTimes(1);
|
|
506
|
+
jest.advanceTimersByTime(1000);
|
|
507
|
+
expect(server.listen).toHaveBeenCalledTimes(2);
|
|
506
508
|
});
|
|
507
509
|
|
|
508
510
|
it('should return credentials JSON', async () => {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const result = await
|
|
511
|
+
const promise = original.createCredentials(profile, auth);
|
|
512
|
+
makeRequest(tokenUrl);
|
|
513
|
+
const result = await promise;
|
|
512
514
|
|
|
513
515
|
expect(result).toEqual(credentialsJSON);
|
|
514
516
|
});
|
package/src/lib/secrets.ts
CHANGED
|
@@ -13,10 +13,12 @@ import secrets from './secrets';
|
|
|
13
13
|
export { getSecrets, getCredentials };
|
|
14
14
|
export default { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
16
|
+
const port = 6006;
|
|
17
|
+
const host = `localhost:${port}`;
|
|
18
|
+
const startURI = `http://${host}/`;
|
|
19
|
+
const callbackURI = `http://${host}/oauthcallback`;
|
|
20
|
+
const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
|
|
21
|
+
const serverRetryInterval = 1000;
|
|
20
22
|
|
|
21
23
|
function getScopes(): string[] {
|
|
22
24
|
const scopesFile = getScopesFile();
|
|
@@ -81,7 +83,10 @@ async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Cl
|
|
|
81
83
|
scope,
|
|
82
84
|
});
|
|
83
85
|
|
|
84
|
-
const server = http.createServer(
|
|
86
|
+
const server = http.createServer();
|
|
87
|
+
enableDestroy(server);
|
|
88
|
+
|
|
89
|
+
server.on('request', async (request, response) => {
|
|
85
90
|
if (!request.url) {
|
|
86
91
|
response.end('');
|
|
87
92
|
return;
|
|
@@ -102,10 +107,20 @@ async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Cl
|
|
|
102
107
|
resolve(tokens);
|
|
103
108
|
});
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
server.on('error', (error: NodeJS.ErrnoException) => {
|
|
111
|
+
if (error.code === 'EADDRINUSE') {
|
|
112
|
+
setTimeout(() => server.listen(port), serverRetryInterval);
|
|
113
|
+
} else {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
server.once('listening', () => {
|
|
119
|
+
warn('Please check your browser for further actions');
|
|
120
|
+
open(startURI);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
server.listen(port);
|
|
109
124
|
});
|
|
110
125
|
}
|
|
111
126
|
|