@anmiles/google-api-wrapper 6.1.0 → 7.0.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/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ 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
+ ## [7.0.0](../../tags/v7.0.0) - 2023-04-22
9
+ ### Changed
10
+ - Invalidate tokens after 7 days due to Google policy for testing apps
11
+
12
+ ## [6.1.1](../../tags/v6.1.1) - 2023-03-27
13
+ ### Changed
14
+ - Immediately destroy server after receiving needed response
15
+
8
16
  ## [6.1.0](../../tags/v6.1.0) - 2023-03-24
9
17
  ### Added
10
18
  - Overriding scopes for `getAuth` and `getAPI` functions
@@ -7,8 +7,8 @@ declare const _default: {
7
7
  checkJSON: typeof checkJSON;
8
8
  };
9
9
  export default _default;
10
- declare function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>): T;
11
- declare function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>): Promise<T>;
10
+ declare function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>, validateJSON?: (json: T) => boolean): T;
11
+ declare function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>, validateJSONAsync?: (json: T) => Promise<boolean>): Promise<T>;
12
12
  declare function writeJSON<T>(filename: string, json: T): void;
13
13
  declare function readJSON<T>(filename: string): T;
14
14
  declare function checkJSON<T>(filename: string, json: T): void;
@@ -9,9 +9,12 @@ const logger_1 = require("./logger");
9
9
  const paths_1 = require("./paths");
10
10
  const jsonLib_1 = __importDefault(require("./jsonLib"));
11
11
  exports.default = { getJSON, getJSONAsync, writeJSON, readJSON, checkJSON };
12
- function getJSON(filename, createCallback) {
12
+ function getJSON(filename, createCallback, validateJSON) {
13
13
  if (fs_1.default.existsSync(filename)) {
14
- return jsonLib_1.default.readJSON(filename);
14
+ const json = jsonLib_1.default.readJSON(filename);
15
+ if (!validateJSON || validateJSON(json)) {
16
+ return json;
17
+ }
15
18
  }
16
19
  const json = createCallback();
17
20
  jsonLib_1.default.checkJSON(filename, json);
@@ -20,12 +23,16 @@ function getJSON(filename, createCallback) {
20
23
  return json;
21
24
  }
22
25
  exports.getJSON = getJSON;
23
- async function getJSONAsync(filename, createCallbackAsync) {
26
+ async function getJSONAsync(filename, createCallbackAsync, validateJSONAsync) {
24
27
  if (fs_1.default.existsSync(filename)) {
25
- return jsonLib_1.default.readJSON(filename);
28
+ const json = jsonLib_1.default.readJSON(filename);
29
+ if (!validateJSONAsync || await validateJSONAsync(json)) {
30
+ return json;
31
+ }
26
32
  }
27
33
  const json = await createCallbackAsync();
28
34
  jsonLib_1.default.checkJSON(filename, json);
35
+ (0, paths_1.ensureFile)(filename);
29
36
  jsonLib_1.default.writeJSON(filename, json);
30
37
  return json;
31
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"jsonLib.js","sourceRoot":"","sources":["../../src/lib/jsonLib.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,qCAAiC;AACjC,mCAAqC;AAErC,wDAAgC;AAGhC,kBAAe,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAEzE,SAAS,OAAO,CAAI,QAAgB,EAAE,cAA8C;IACnF,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,OAAO,iBAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;KAClC;IAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAbQ,0BAAO;AAehB,KAAK,UAAU,YAAY,CAAI,QAAgB,EAAE,mBAAqC;IACrF,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,OAAO,iBAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;KAClC;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACzC,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAxBiB,oCAAY;AA0B9B,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtD,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AA7B+B,8BAAS;AA+BzC,SAAS,QAAQ,CAAI,QAAgB;IACpC,MAAM,UAAU,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAM,CAAC;AACpC,CAAC;AAED,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,IAAI,IAAI,EAAE;QACT,OAAO;KACP;IACD,IAAA,cAAK,EAAC,QAAQ,QAAQ,sGAAsG,CAAC,CAAC;AAC/H,CAAC"}
1
+ {"version":3,"file":"jsonLib.js","sourceRoot":"","sources":["../../src/lib/jsonLib.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,qCAAiC;AACjC,mCAAqC;AAErC,wDAAgC;AAGhC,kBAAe,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAEzE,SAAS,OAAO,CAAI,QAAgB,EAAE,cAA8C,EAAE,YAAmC;IACxH,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,GAAG,iBAAO,CAAC,QAAQ,CAAI,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE;YACxC,OAAO,IAAI,CAAC;SACZ;KACD;IAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAjBQ,0BAAO;AAmBhB,KAAK,UAAU,YAAY,CAAI,QAAgB,EAAE,mBAAqC,EAAE,iBAAiD;IACxI,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,GAAG,iBAAO,CAAC,QAAQ,CAAI,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,iBAAiB,IAAI,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE;YACxD,OAAO,IAAI,CAAC;SACZ;KACD;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACzC,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAjCiB,oCAAY;AAmC9B,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtD,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAtC+B,8BAAS;AAwCzC,SAAS,QAAQ,CAAI,QAAgB;IACpC,MAAM,UAAU,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAM,CAAC;AACpC,CAAC;AAED,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,IAAI,IAAI,EAAE;QACT,OAAO;KACP;IACD,IAAA,cAAK,EAAC,QAAQ,QAAQ,sGAAsG,CAAC,CAAC;AAC/H,CAAC"}
@@ -5,6 +5,7 @@ declare const _default: {
5
5
  getScopes: typeof getScopes;
6
6
  getSecrets: typeof getSecrets;
7
7
  getCredentials: typeof getCredentials;
8
+ validateCredentials: typeof validateCredentials;
8
9
  createCredentials: typeof createCredentials;
9
10
  checkSecrets: typeof checkSecrets;
10
11
  getSecretsError: typeof getSecretsError;
@@ -14,6 +15,7 @@ export default _default;
14
15
  declare function getScopes(): string[];
15
16
  declare function getSecrets(profile: string): Secrets;
16
17
  declare function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials>;
18
+ declare function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<boolean>;
17
19
  declare function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials>;
18
20
  declare function checkSecrets(profile: string, secretsObject: Secrets, secretsFile: string): true | void;
19
21
  declare function getScopesError(scopesFile: string): string;
@@ -28,14 +28,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.getCredentials = exports.getSecrets = void 0;
30
30
  const http_1 = __importDefault(require("http"));
31
+ const server_destroy_1 = __importDefault(require("server-destroy"));
31
32
  const colorette = __importStar(require("colorette"));
33
+ const open_1 = __importDefault(require("open"));
32
34
  const jsonLib_1 = require("./jsonLib");
33
35
  const logger_1 = require("./logger");
34
36
  const paths_1 = require("./paths");
35
37
  const secrets_1 = __importDefault(require("./secrets"));
36
- exports.default = { getScopes, getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
38
+ exports.default = { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
37
39
  const callbackPort = 6006;
38
40
  const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
41
+ const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
39
42
  function getScopes() {
40
43
  const scopesFile = (0, paths_1.getScopesFile)();
41
44
  const scopes = (0, jsonLib_1.getJSON)(scopesFile, () => (0, logger_1.error)(secrets_1.default.getScopesError(scopesFile)));
@@ -52,9 +55,18 @@ async function getCredentials(profile, auth, options) {
52
55
  const credentialsFile = (0, paths_1.getCredentialsFile)(profile);
53
56
  return (options === null || options === void 0 ? void 0 : options.temporary)
54
57
  ? secrets_1.default.createCredentials(profile, auth, options)
55
- : (0, jsonLib_1.getJSONAsync)(credentialsFile, () => secrets_1.default.createCredentials(profile, auth, options));
58
+ : (0, jsonLib_1.getJSONAsync)(credentialsFile, () => secrets_1.default.createCredentials(profile, auth, options), secrets_1.default.validateCredentials);
56
59
  }
57
60
  exports.getCredentials = getCredentials;
61
+ async function validateCredentials(credentials) {
62
+ if (!credentials.access_token) {
63
+ return false;
64
+ }
65
+ if (!credentials.expiry_date) {
66
+ return true;
67
+ }
68
+ return new Date().getTime() - credentials.expiry_date < tokenExpiration;
69
+ }
58
70
  async function createCredentials(profile, auth, options) {
59
71
  const scope = (options === null || options === void 0 ? void 0 : options.scopes) || secrets_1.default.getScopes();
60
72
  return new Promise((resolve) => {
@@ -71,13 +83,19 @@ async function createCredentials(profile, auth, options) {
71
83
  if (!code) {
72
84
  return;
73
85
  }
74
- server.close();
86
+ server.destroy();
75
87
  const { tokens } = await auth.getToken(code);
76
88
  resolve(tokens);
77
89
  }
78
90
  });
91
+ (0, server_destroy_1.default)(server);
79
92
  server.listen(callbackPort);
80
- (0, logger_1.info)(`Please open ${colorette.yellow(authUrl)} in your browser using google profile for ${colorette.yellow(profile)} and allow access to ${colorette.yellow(scope.join(','))}`);
93
+ if (options === null || options === void 0 ? void 0 : options.temporary) {
94
+ (0, logger_1.info)(`Please open ${colorette.yellow(authUrl)} in your browser using google profile for ${colorette.yellow(profile)} and allow access to ${colorette.yellow(scope.join(','))}`);
95
+ }
96
+ else {
97
+ (0, open_1.default)(authUrl);
98
+ }
81
99
  });
82
100
  }
83
101
  function checkSecrets(profile, secretsObject, secretsFile) {
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"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"}
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": "6.1.0",
3
+ "version": "7.0.0",
4
4
  "description": "Provides quick interface for getting google API data",
5
5
  "keywords": [
6
6
  "google",
@@ -31,10 +31,13 @@
31
31
  "dependencies": {
32
32
  "colorette": "^2.0.19",
33
33
  "execa": "^5.1.1",
34
- "googleapis": "^104.0.0"
34
+ "googleapis": "^104.0.0",
35
+ "open": "^9.1.0",
36
+ "server-destroy": "^1.0.1"
35
37
  },
36
38
  "devDependencies": {
37
39
  "@types/jest": "^28.1.3",
40
+ "@types/server-destroy": "^1.0.1",
38
41
  "@typescript-eslint/eslint-plugin": "^5.30.0",
39
42
  "@typescript-eslint/parser": "^5.30.0",
40
43
  "eslint": "^8.18.0",
@@ -33,11 +33,15 @@ const json = { key : 'value' };
33
33
  const jsonString = JSON.stringify(json, null, ' ');
34
34
  const fallbackJSON = { fallbackKey : 'fallbackValue' };
35
35
 
36
+ let fileExists: boolean;
37
+ let validation: boolean;
38
+
39
+ const validateCallback = jest.fn().mockImplementation(() => validation);
40
+ const validateCallbackAsync = jest.fn().mockImplementation(async () => validation);
41
+
36
42
  const createCallback = jest.fn().mockReturnValue(fallbackJSON);
37
43
  const createCallbackAsync = jest.fn().mockResolvedValue(fallbackJSON);
38
44
 
39
- let fileExists: boolean;
40
-
41
45
  describe('src/lib/jsonLib', () => {
42
46
  describe('readJSON', () => {
43
47
  it('should read specified file', () => {
@@ -62,91 +66,175 @@ describe('src/lib/jsonLib', () => {
62
66
  });
63
67
 
64
68
  describe('getJSON', () => {
65
- it('should call readJSON if file exists', () => {
69
+
70
+ it('should call readJSON if file exists and json is valid', () => {
66
71
  fileExists = true;
72
+ validation = true;
67
73
 
68
- original.getJSON(filename, createCallback);
74
+ original.getJSON(filename, createCallback, validateCallback);
69
75
 
70
76
  expect(jsonLib.readJSON).toBeCalledWith(filename);
71
77
  expect(createCallback).not.toBeCalled();
72
78
  });
73
79
 
80
+ it('should call createCallback if file exists but json is not valid', () => {
81
+ fileExists = true;
82
+ validation = false;
83
+
84
+ original.getJSON(filename, createCallback, validateCallback);
85
+
86
+ expect(jsonLib.readJSON).toBeCalledWith(filename);
87
+ expect(createCallback).toBeCalledWith();
88
+ });
89
+
74
90
  it('should call createCallback if file not exists', () => {
75
91
  fileExists = false;
76
92
 
77
- original.getJSON(filename, createCallback);
93
+ original.getJSON(filename, createCallback, validateCallback);
78
94
 
79
95
  expect(jsonLib.readJSON).not.toBeCalled();
80
96
  expect(createCallback).toBeCalledWith();
81
97
  });
82
98
 
99
+ it('should not write fallback JSON back if file exists and json is valid', () => {
100
+ fileExists = true;
101
+ validation = true;
102
+
103
+ original.getJSON(filename, createCallback, validateCallback);
104
+
105
+ expect(jsonLib.writeJSON).not.toBeCalled();
106
+ });
107
+
108
+ it('should write fallback JSON back if file exists but json is not valid', () => {
109
+ fileExists = true;
110
+ validation = false;
111
+
112
+ original.getJSON(filename, createCallback, validateCallback);
113
+
114
+ expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
115
+ expect(paths.ensureFile).toBeCalledWith(filename);
116
+ expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
117
+ });
118
+
83
119
  it('should write fallback JSON back if file not exists', () => {
84
120
  fileExists = false;
85
121
 
86
- original.getJSON(filename, createCallback);
122
+ original.getJSON(filename, createCallback, validateCallback);
87
123
 
88
124
  expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
89
125
  expect(paths.ensureFile).toBeCalledWith(filename);
90
126
  expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
91
127
  });
92
128
 
93
- it('should return JSON if file exists', () => {
129
+ it('should return JSON if file exists and json is valid', () => {
94
130
  fileExists = true;
131
+ validation = true;
95
132
 
96
- const result = original.getJSON(filename, createCallback);
133
+ const result = original.getJSON(filename, createCallback, validateCallback);
97
134
 
98
135
  expect(result).toEqual(json);
99
136
  });
100
137
 
138
+ it('should return fallback JSON if file exists but json is not valid', () => {
139
+ fileExists = true;
140
+ validation = false;
141
+
142
+ const result = original.getJSON(filename, createCallback, validateCallback);
143
+
144
+ expect(result).toEqual(fallbackJSON);
145
+ });
146
+
101
147
  it('should return fallback JSON if file not exists', () => {
102
148
  fileExists = false;
103
149
 
104
- const result = original.getJSON(filename, createCallback);
150
+ const result = original.getJSON(filename, createCallback, validateCallback);
105
151
 
106
152
  expect(result).toEqual(fallbackJSON);
107
153
  });
108
154
  });
109
155
 
110
156
  describe('getJSONAsync', () => {
111
- it('should call readJSON if file exists', async () => {
157
+ it('should call readJSON if file exists and json is valid', async () => {
112
158
  fileExists = true;
159
+ validation = true;
113
160
 
114
- await original.getJSONAsync(filename, createCallbackAsync);
161
+ await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
115
162
 
116
163
  expect(jsonLib.readJSON).toBeCalledWith(filename);
117
164
  expect(createCallbackAsync).not.toBeCalled();
118
165
  });
119
166
 
167
+ it('should call createCallback if file exists but json is not valid', async () => {
168
+ fileExists = true;
169
+ validation = false;
170
+
171
+ await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
172
+
173
+ expect(jsonLib.readJSON).toBeCalledWith(filename);
174
+ expect(createCallbackAsync).toBeCalledWith();
175
+ });
176
+
120
177
  it('should call createCallback if file not exists', async () => {
121
178
  fileExists = false;
122
179
 
123
- await original.getJSONAsync(filename, createCallbackAsync);
180
+ await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
124
181
 
125
182
  expect(jsonLib.readJSON).not.toBeCalled();
126
183
  expect(createCallbackAsync).toBeCalledWith();
127
184
  });
128
185
 
186
+ it('should not write fallback JSON back if file exists and json is valid', async () => {
187
+ fileExists = true;
188
+ validation = true;
189
+
190
+ await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
191
+
192
+ expect(jsonLib.writeJSON).not.toBeCalled();
193
+ });
194
+
195
+ it('should write fallback JSON back if file exists but json is not valid', async () => {
196
+ fileExists = true;
197
+ validation = false;
198
+
199
+ await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
200
+
201
+ expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
202
+ expect(paths.ensureFile).toBeCalledWith(filename);
203
+ expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
204
+ });
205
+
129
206
  it('should write fallback JSON back if file not exists', async () => {
130
207
  fileExists = false;
131
208
 
132
- await original.getJSONAsync(filename, createCallbackAsync);
209
+ await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
133
210
 
134
211
  expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
212
+ expect(paths.ensureFile).toBeCalledWith(filename);
135
213
  expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
136
214
  });
137
215
 
138
- it('should return JSON if file exists', async () => {
216
+ it('should return JSON if file exists and json is valid', async () => {
139
217
  fileExists = true;
218
+ validation = true;
140
219
 
141
- const result = await original.getJSONAsync(filename, createCallbackAsync);
220
+ const result = await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
142
221
 
143
222
  expect(result).toEqual(json);
144
223
  });
145
224
 
225
+ it('should return fallback JSON if file exists but json is not valid', async () => {
226
+ fileExists = true;
227
+ validation = false;
228
+
229
+ const result = await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
230
+
231
+ expect(result).toEqual(fallbackJSON);
232
+ });
233
+
146
234
  it('should return fallback JSON if file not exists', async () => {
147
235
  fileExists = false;
148
236
 
149
- const result = await original.getJSONAsync(filename, createCallbackAsync);
237
+ const result = await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
150
238
 
151
239
  expect(result).toEqual(fallbackJSON);
152
240
  });
@@ -1,6 +1,7 @@
1
1
  import http from 'http';
2
2
  import path from 'path';
3
3
  import * as colorette from 'colorette';
4
+ import open from 'open';
4
5
  import type GoogleApis from 'googleapis';
5
6
  import jsonLib from '../jsonLib';
6
7
  import logger from '../logger';
@@ -9,13 +10,14 @@ import type { Secrets } from '../../types';
9
10
  import secrets from '../secrets';
10
11
  const original = jest.requireActual('../secrets').default as typeof secrets;
11
12
  jest.mock<typeof secrets>('../secrets', () => ({
12
- getScopes : jest.fn().mockImplementation(() => scopesJSON),
13
- getSecrets : jest.fn().mockImplementation(() => secretsJSON),
14
- getCredentials : jest.fn(),
15
- createCredentials : jest.fn(),
16
- checkSecrets : jest.fn(),
17
- getScopesError : jest.fn().mockImplementation(() => scopesError),
18
- getSecretsError : jest.fn().mockImplementation(() => secretsError),
13
+ getScopes : jest.fn().mockImplementation(() => scopesJSON),
14
+ getSecrets : jest.fn().mockImplementation(() => secretsJSON),
15
+ getCredentials : jest.fn(),
16
+ validateCredentials : jest.fn(),
17
+ createCredentials : jest.fn(),
18
+ checkSecrets : jest.fn(),
19
+ getScopesError : jest.fn().mockImplementation(() => scopesError),
20
+ getSecretsError : jest.fn().mockImplementation(() => secretsError),
19
21
  }));
20
22
 
21
23
  jest.mock<Partial<typeof http>>('http', () => ({
@@ -23,8 +25,10 @@ jest.mock<Partial<typeof http>>('http', () => ({
23
25
  serverCallback = callback;
24
26
 
25
27
  return {
28
+ on,
26
29
  listen,
27
30
  close,
31
+ destroy,
28
32
  };
29
33
  }),
30
34
  }));
@@ -37,6 +41,8 @@ jest.mock<Partial<typeof colorette>>('colorette', () => ({
37
41
  yellow : jest.fn().mockImplementation((text) => `yellow:${text}`),
38
42
  }));
39
43
 
44
+ jest.mock<Partial<typeof open>>('open', () => jest.fn());
45
+
40
46
  jest.mock<Partial<typeof jsonLib>>('../jsonLib', () => ({
41
47
  getJSON : jest.fn().mockImplementation(() => json),
42
48
  getJSONAsync : jest.fn().mockImplementation(async () => json),
@@ -87,7 +93,7 @@ const code = 'code';
87
93
  const authUrl = 'https://authUrl';
88
94
  const auth = {
89
95
  generateAuthUrl : jest.fn().mockReturnValue(authUrl),
90
- getToken : jest.fn().mockReturnValue({ tokens : credentialsJSON }),
96
+ getToken : jest.fn().mockResolvedValue({ tokens : credentialsJSON }),
91
97
  } as unknown as GoogleApis.Common.OAuth2Client;
92
98
 
93
99
  let request: http.IncomingMessage;
@@ -103,10 +109,24 @@ let serverCallback: (
103
109
 
104
110
  let closedTime: number;
105
111
 
112
+ const on = jest.fn().mockImplementation((event: string, listener: (...args: any[]) => void) => {
113
+ if (event === 'connection') {
114
+ // always simulate opening several connections once connections are meant to be listened
115
+ connections.forEach((connection) => listener(connection));
116
+ }
117
+ });
118
+
106
119
  const listen = jest.fn();
107
120
  const close = jest.fn().mockImplementation(() => {
108
121
  closedTime = new Date().getTime();
109
122
  });
123
+ const destroy = jest.fn();
124
+
125
+ const connections = [
126
+ { remoteAddress : 'server', remotePort : '1001', on : jest.fn(), destroy : jest.fn() },
127
+ { remoteAddress : 'server', remotePort : '1002', on : jest.fn(), destroy : jest.fn() },
128
+ { remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
129
+ ];
110
130
 
111
131
  describe('src/lib/secrets', () => {
112
132
  describe('getScopes', () => {
@@ -239,6 +259,31 @@ describe('src/lib/secrets', () => {
239
259
  });
240
260
  });
241
261
 
262
+ describe('validateCredentials', () => {
263
+ it('should return false if no access token', async () => {
264
+ expect(await original.validateCredentials({})).toEqual(false);
265
+ });
266
+
267
+ it('should return true if no expiration', async () => {
268
+ // eslint-disable-next-line camelcase
269
+ expect(await original.validateCredentials({ access_token : 'token' })).toEqual(true);
270
+ });
271
+
272
+ it('should return true if credentials are not more than 1 week ago', async () => {
273
+ const expiryDate = new Date();
274
+ expiryDate.setDate(expiryDate.getDate() - 6);
275
+ // eslint-disable-next-line camelcase
276
+ expect(await original.validateCredentials({ access_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(true);
277
+ });
278
+
279
+ it('should return true if credentials are more than 1 week ago', async () => {
280
+ const expiryDate = new Date();
281
+ expiryDate.setDate(expiryDate.getDate() - 8);
282
+ // eslint-disable-next-line camelcase
283
+ expect(await original.validateCredentials({ access_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(false);
284
+ });
285
+ });
286
+
242
287
  describe('createCredentials', () => {
243
288
  function willOpen(request: http.IncomingMessage, timeout: number) {
244
289
  setTimeout(async () => {
@@ -291,19 +336,30 @@ describe('src/lib/secrets', () => {
291
336
  expect(listen).toBeCalledWith(6006);
292
337
  });
293
338
 
294
- it('should ask to open browser page', async () => {
339
+ it('should open browser page without prompt if this is permanent request that tends to save credentials in the file', async () => {
295
340
  willOpen(request, 100);
296
341
 
297
342
  await original.createCredentials(profile, auth);
298
343
 
344
+ expect(open).toBeCalledWith('https://authUrl');
345
+ expect(logger.info).not.toBeCalled();
346
+ });
347
+
348
+ it('should ask to open browser page', async () => {
349
+ willOpen(request, 100);
350
+
351
+ await original.createCredentials(profile, auth, { temporary : true });
352
+
353
+ expect(open).not.toBeCalled();
299
354
  expect(logger.info).toBeCalledWith(`Please open yellow:https://authUrl in your browser using google profile for yellow:${profile} and allow access to yellow:https://www.googleapis.com/auth/calendar.calendars.readonly,https://www.googleapis.com/auth/calendar.events.readonly`);
300
355
  });
301
356
 
302
357
  it('should ask to open browser page with custom scopes', async () => {
303
358
  willOpen(request, 100);
304
359
 
305
- await original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
360
+ await original.createCredentials(profile, auth, { temporary : true, scopes : [ 'scope1', 'scope2' ] });
306
361
 
362
+ expect(open).not.toBeCalled();
307
363
  expect(logger.info).toBeCalledWith(`Please open yellow:https://authUrl in your browser using google profile for yellow:${profile} and allow access to yellow:scope1,scope2`);
308
364
  });
309
365
 
@@ -315,12 +371,14 @@ describe('src/lib/secrets', () => {
315
371
  expect(response.end).toBeCalledWith('<h1>Please close this page and return to application. Wait for application to be finished automatically.</h1>');
316
372
  });
317
373
 
318
- it('should close server if request.url is truthy', async () => {
374
+ it('should close server and destroy all connections if request.url is truthy', async () => {
319
375
  willOpen(request, 100);
320
376
 
321
377
  await original.createCredentials(profile, auth);
322
378
 
323
379
  expect(close).toBeCalled();
380
+
381
+ connections.forEach((connection) => expect(connection.destroy).toBeCalled());
324
382
  });
325
383
 
326
384
  it('should only resolve when request.url is truthy', async () => {
@@ -7,9 +7,13 @@ import jsonLib from './jsonLib';
7
7
  export { getJSON, getJSONAsync, writeJSON };
8
8
  export default { getJSON, getJSONAsync, writeJSON, readJSON, checkJSON };
9
9
 
10
- function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>): T {
10
+ function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>, validateJSON?: (json: T) => boolean): T {
11
11
  if (fs.existsSync(filename)) {
12
- return jsonLib.readJSON(filename);
12
+ const json = jsonLib.readJSON<T>(filename);
13
+
14
+ if (!validateJSON || validateJSON(json)) {
15
+ return json;
16
+ }
13
17
  }
14
18
 
15
19
  const json = createCallback();
@@ -19,13 +23,18 @@ function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<a
19
23
  return json;
20
24
  }
21
25
 
22
- async function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>): Promise<T> {
26
+ async function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>, validateJSONAsync?: (json: T) => Promise<boolean>): Promise<T> {
23
27
  if (fs.existsSync(filename)) {
24
- return jsonLib.readJSON(filename);
28
+ const json = jsonLib.readJSON<T>(filename);
29
+
30
+ if (!validateJSONAsync || await validateJSONAsync(json)) {
31
+ return json;
32
+ }
25
33
  }
26
34
 
27
35
  const json = await createCallbackAsync();
28
36
  jsonLib.checkJSON(filename, json);
37
+ ensureFile(filename);
29
38
  jsonLib.writeJSON(filename, json);
30
39
  return json;
31
40
  }
@@ -1,5 +1,7 @@
1
1
  import http from 'http';
2
+ import enableDestroy from 'server-destroy';
2
3
  import * as colorette from 'colorette';
4
+ import open from 'open';
3
5
  import type GoogleApis from 'googleapis';
4
6
  import type { Secrets, AuthOptions } from '../types';
5
7
  import { getJSON, getJSONAsync } from './jsonLib';
@@ -9,10 +11,11 @@ import { getScopesFile, getSecretsFile, getCredentialsFile } from './paths';
9
11
  import secrets from './secrets';
10
12
 
11
13
  export { getSecrets, getCredentials };
12
- export default { getScopes, getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
14
+ export default { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
13
15
 
14
- const callbackPort = 6006;
15
- const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
16
+ const callbackPort = 6006;
17
+ const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
18
+ const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
16
19
 
17
20
  function getScopes(): string[] {
18
21
  const scopesFile = getScopesFile();
@@ -32,7 +35,19 @@ async function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Cli
32
35
 
33
36
  return options?.temporary
34
37
  ? secrets.createCredentials(profile, auth, options)
35
- : getJSONAsync(credentialsFile, () => secrets.createCredentials(profile, auth, options));
38
+ : getJSONAsync(credentialsFile, () => secrets.createCredentials(profile, auth, options), secrets.validateCredentials);
39
+ }
40
+
41
+ async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<boolean> {
42
+ if (!credentials.access_token) {
43
+ return false;
44
+ }
45
+
46
+ if (!credentials.expiry_date) {
47
+ return true;
48
+ }
49
+
50
+ return new Date().getTime() - credentials.expiry_date < tokenExpiration;
36
51
  }
37
52
 
38
53
  async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials> {
@@ -56,14 +71,20 @@ async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Cl
56
71
  return;
57
72
  }
58
73
 
59
- server.close();
74
+ server.destroy();
60
75
  const { tokens } = await auth.getToken(code);
61
76
  resolve(tokens);
62
77
  }
63
78
  });
64
79
 
80
+ enableDestroy(server);
65
81
  server.listen(callbackPort);
66
- info(`Please open ${colorette.yellow(authUrl)} in your browser using google profile for ${colorette.yellow(profile)} and allow access to ${colorette.yellow(scope.join(','))}`);
82
+
83
+ if (options?.temporary) {
84
+ info(`Please open ${colorette.yellow(authUrl)} in your browser using google profile for ${colorette.yellow(profile)} and allow access to ${colorette.yellow(scope.join(','))}`);
85
+ } else {
86
+ open(authUrl);
87
+ }
67
88
  });
68
89
  }
69
90