@anmiles/google-api-wrapper 2.1.2 → 3.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 +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api/calendar.d.ts +2 -2
- package/dist/lib/auth.js +4 -1
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/paths.d.ts +3 -1
- package/dist/lib/paths.js +6 -2
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/secrets.d.ts +5 -1
- package/dist/lib/secrets.js +45 -11
- package/dist/lib/secrets.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/lib/__tests__/auth.test.ts +16 -1
- package/src/lib/__tests__/paths.test.ts +10 -0
- package/src/lib/__tests__/secrets.test.ts +105 -35
- package/src/lib/api/__tests__/calendar.test.ts +10 -10
- package/src/lib/api/__tests__/youtube.test.ts +5 -5
- package/src/lib/api/calendar.ts +2 -2
- package/src/lib/auth.ts +4 -1
- package/src/lib/paths.ts +6 -2
- package/src/lib/secrets.ts +49 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,11 +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
|
-
## [
|
|
8
|
+
## [3.0.0](../../tags/v3.0.0) - 2023-03-13
|
|
9
9
|
### Changed
|
|
10
|
-
-
|
|
10
|
+
- Revised auth instructions
|
|
11
|
+
- Scopes can be set per end-project
|
|
11
12
|
|
|
12
|
-
## [2.1.
|
|
13
|
+
## [2.1.3](../../tags/v2.1.3) - 2023-03-13
|
|
13
14
|
### Changed
|
|
14
15
|
- Fixed exported types
|
|
15
16
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export * as calendar from './lib/api/calendar';
|
|
|
2
2
|
export * as youtube from './lib/api/youtube';
|
|
3
3
|
export { getItems } from './lib/api/shared';
|
|
4
4
|
export { createProfile, getProfiles } from './lib/profiles';
|
|
5
|
-
export { login } from './lib/auth';
|
|
5
|
+
export { login, getAuth } from './lib/auth';
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.login = exports.getProfiles = exports.createProfile = exports.getItems = exports.youtube = exports.calendar = void 0;
|
|
26
|
+
exports.getAuth = exports.login = exports.getProfiles = exports.createProfile = exports.getItems = exports.youtube = exports.calendar = void 0;
|
|
27
27
|
exports.calendar = __importStar(require("./lib/api/calendar"));
|
|
28
28
|
exports.youtube = __importStar(require("./lib/api/youtube"));
|
|
29
29
|
var shared_1 = require("./lib/api/shared");
|
|
@@ -33,4 +33,5 @@ Object.defineProperty(exports, "createProfile", { enumerable: true, get: functio
|
|
|
33
33
|
Object.defineProperty(exports, "getProfiles", { enumerable: true, get: function () { return profiles_1.getProfiles; } });
|
|
34
34
|
var auth_1 = require("./lib/auth");
|
|
35
35
|
Object.defineProperty(exports, "login", { enumerable: true, get: function () { return auth_1.login; } });
|
|
36
|
+
Object.defineProperty(exports, "getAuth", { enumerable: true, get: function () { return auth_1.getAuth; } });
|
|
36
37
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+DAA+C;AAC/C,6DAA6C;AAC7C,2CAA4C;AAAnC,kGAAA,QAAQ,OAAA;AACjB,2CAA4D;AAAnD,yGAAA,aAAa,OAAA;AAAE,uGAAA,WAAW,OAAA;AACnC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+DAA+C;AAC/C,6DAA6C;AAC7C,2CAA4C;AAAnC,kGAAA,QAAQ,OAAA;AACjB,2CAA4D;AAAnD,yGAAA,aAAa,OAAA;AAAE,uGAAA,WAAW,OAAA;AACnC,mCAA4C;AAAnC,6FAAA,KAAK,OAAA;AAAE,+FAAA,OAAO,OAAA"}
|
|
@@ -8,6 +8,6 @@ declare const _default: {
|
|
|
8
8
|
};
|
|
9
9
|
export default _default;
|
|
10
10
|
declare function getAPI(profile: string): Promise<GoogleApis.calendar_v3.Calendar>;
|
|
11
|
-
declare function getCalendars(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Calendarlist$List): Promise<GoogleApis.calendar_v3.Schema$
|
|
12
|
-
declare function getEvents(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Events$List): Promise<GoogleApis.calendar_v3.Schema$
|
|
11
|
+
declare function getCalendars(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Calendarlist$List): Promise<GoogleApis.calendar_v3.Schema$CalendarListEntry[]>;
|
|
12
|
+
declare function getEvents(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Events$List): Promise<GoogleApis.calendar_v3.Schema$Event[]>;
|
|
13
13
|
declare function setEvent(profile: string, eventId: string | undefined, args: GoogleApis.calendar_v3.Params$Resource$Events$Update): Promise<void>;
|
package/dist/lib/auth.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getAuth = exports.login = void 0;
|
|
7
7
|
const googleapis_1 = require("googleapis");
|
|
8
|
+
const logger_1 = require("./logger");
|
|
8
9
|
const profiles_1 = require("./profiles");
|
|
9
10
|
const secrets_1 = require("./secrets");
|
|
10
11
|
const auth_1 = __importDefault(require("./auth"));
|
|
@@ -12,12 +13,14 @@ exports.default = { login, getAuth };
|
|
|
12
13
|
async function login(profile) {
|
|
13
14
|
const profiles = (0, profiles_1.getProfiles)().filter((p) => !profile || p === profile);
|
|
14
15
|
for (const profile of profiles) {
|
|
16
|
+
(0, logger_1.warn)(`${profile} - logging in...`);
|
|
15
17
|
await auth_1.default.getAuth(profile);
|
|
18
|
+
(0, logger_1.info)(`${profile} - logged in successfully`);
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
exports.login = login;
|
|
19
22
|
async function getAuth(profile) {
|
|
20
|
-
const secrets =
|
|
23
|
+
const secrets = (0, secrets_1.getSecrets)(profile);
|
|
21
24
|
const googleAuth = new googleapis_1.google.auth.OAuth2(secrets.web.client_id, secrets.web.client_secret, secrets.web.redirect_uris[0]);
|
|
22
25
|
const tokens = await (0, secrets_1.getCredentials)(profile, googleAuth);
|
|
23
26
|
googleAuth.setCredentials(tokens);
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":";;;;;;AAAA,2CAAoC;AAEpC,yCAAyC;AACzC,uCAAuD;AAEvD,kDAA0B;AAG1B,kBAAe,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAElC,KAAK,UAAU,KAAK,CAAC,OAAgB;IACpC,MAAM,QAAQ,GAAG,IAAA,sBAAW,GAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC;IAExE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;QAC/B,MAAM,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":";;;;;;AAAA,2CAAoC;AAEpC,qCAAsC;AACtC,yCAAyC;AACzC,uCAAuD;AAEvD,kDAA0B;AAG1B,kBAAe,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAElC,KAAK,UAAU,KAAK,CAAC,OAAgB;IACpC,MAAM,QAAQ,GAAG,IAAA,sBAAW,GAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC;IAExE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;QAC/B,IAAA,aAAI,EAAC,GAAG,OAAO,kBAAkB,CAAC,CAAC;QACnC,MAAM,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAA,aAAI,EAAC,GAAG,OAAO,2BAA2B,CAAC,CAAC;KAC5C;AACF,CAAC;AAXQ,sBAAK;AAad,KAAK,UAAU,OAAO,CAAC,OAAe;IACrC,MAAM,OAAO,GAAG,IAAA,oBAAU,EAAC,OAAO,CAAC,CAAC;IAEpC,MAAM,UAAU,GAAG,IAAI,mBAAM,CAAC,IAAI,CAAC,MAAM,CACxC,OAAO,CAAC,GAAG,CAAC,SAAS,EACrB,OAAO,CAAC,GAAG,CAAC,aAAa,EACzB,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAC5B,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,IAAA,wBAAc,EAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACzD,UAAU,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,UAAU,CAAC;AACnB,CAAC;AAzBe,0BAAO"}
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { ensureDir, ensureFile, getProfilesFile, getSecretsFile, getCredentialsFile };
|
|
1
|
+
export { ensureDir, ensureFile, getProfilesFile, getScopesFile, getSecretsFile, getCredentialsFile };
|
|
2
2
|
declare const _default: {
|
|
3
3
|
ensureDir: typeof ensureDir;
|
|
4
4
|
ensureFile: typeof ensureFile;
|
|
5
5
|
getProfilesFile: typeof getProfilesFile;
|
|
6
|
+
getScopesFile: typeof getScopesFile;
|
|
6
7
|
getSecretsFile: typeof getSecretsFile;
|
|
7
8
|
getCredentialsFile: typeof getCredentialsFile;
|
|
8
9
|
};
|
|
@@ -10,5 +11,6 @@ export default _default;
|
|
|
10
11
|
declare function ensureDir(dirPath: string): string;
|
|
11
12
|
declare function ensureFile(filePath: string): string;
|
|
12
13
|
declare function getProfilesFile(): string;
|
|
14
|
+
declare function getScopesFile(): string;
|
|
13
15
|
declare function getSecretsFile(profile: string): string;
|
|
14
16
|
declare function getCredentialsFile(profile: string): string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -3,11 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getCredentialsFile = exports.getSecretsFile = exports.getProfilesFile = exports.ensureFile = exports.ensureDir = void 0;
|
|
6
|
+
exports.getCredentialsFile = exports.getSecretsFile = exports.getScopesFile = exports.getProfilesFile = exports.ensureFile = exports.ensureDir = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const paths_1 = __importDefault(require("./paths"));
|
|
10
|
-
exports.default = { ensureDir, ensureFile, getProfilesFile, getSecretsFile, getCredentialsFile };
|
|
10
|
+
exports.default = { ensureDir, ensureFile, getProfilesFile, getScopesFile, getSecretsFile, getCredentialsFile };
|
|
11
11
|
const dirPaths = {
|
|
12
12
|
input: 'input',
|
|
13
13
|
secrets: 'secrets',
|
|
@@ -31,6 +31,10 @@ function getProfilesFile() {
|
|
|
31
31
|
return path_1.default.join(dirPaths.input, 'profiles.json');
|
|
32
32
|
}
|
|
33
33
|
exports.getProfilesFile = getProfilesFile;
|
|
34
|
+
function getScopesFile() {
|
|
35
|
+
return path_1.default.join(dirPaths.input, 'scopes.json');
|
|
36
|
+
}
|
|
37
|
+
exports.getScopesFile = getScopesFile;
|
|
34
38
|
function getSecretsFile(profile) {
|
|
35
39
|
return path_1.default.join(dirPaths.secrets, `${profile}.json`);
|
|
36
40
|
}
|
package/dist/lib/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AACxB,oDAA4B;AAG5B,kBAAe,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AACxB,oDAA4B;AAG5B,kBAAe,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;AAE7G,MAAM,QAAQ,GAAG;IAChB,KAAK,EAAK,OAAO;IACjB,OAAO,EAAG,SAAS;CACnB,CAAC;AAEF,SAAS,SAAS,CAAC,OAAe;IACjC,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE;QAC5B,YAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAG,IAAI,EAAE,CAAC,CAAC;KAC5C;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAbQ,8BAAS;AAelB,SAAS,UAAU,CAAC,QAAgB;IACnC,eAAK,CAAC,SAAS,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAExC,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC7B,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;KAC/B;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAtBmB,gCAAU;AAwB9B,SAAS,eAAe;IACvB,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;AACnD,CAAC;AA1B+B,0CAAe;AA4B/C,SAAS,aAAa;IACrB,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AA9BgD,sCAAa;AAgC9D,SAAS,cAAc,CAAC,OAAe;IACtC,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,OAAO,OAAO,CAAC,CAAC;AACvD,CAAC;AAlC+D,wCAAc;AAoC9E,SAAS,kBAAkB,CAAC,OAAe;IAC1C,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,OAAO,mBAAmB,CAAC,CAAC;AACnE,CAAC;AAtC+E,gDAAkB"}
|
package/dist/lib/secrets.d.ts
CHANGED
|
@@ -2,15 +2,19 @@ import type GoogleApis from 'googleapis';
|
|
|
2
2
|
import type { Secrets } from '../types';
|
|
3
3
|
export { getSecrets, getCredentials };
|
|
4
4
|
declare const _default: {
|
|
5
|
+
getScopes: typeof getScopes;
|
|
5
6
|
getSecrets: typeof getSecrets;
|
|
6
7
|
getCredentials: typeof getCredentials;
|
|
7
8
|
createCredentials: typeof createCredentials;
|
|
8
9
|
checkSecrets: typeof checkSecrets;
|
|
9
10
|
getSecretsError: typeof getSecretsError;
|
|
11
|
+
getScopesError: typeof getScopesError;
|
|
10
12
|
};
|
|
11
13
|
export default _default;
|
|
12
|
-
declare function
|
|
14
|
+
declare function getScopes(): string[];
|
|
15
|
+
declare function getSecrets(profile: string): Secrets;
|
|
13
16
|
declare function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client): Promise<GoogleApis.Auth.Credentials>;
|
|
14
17
|
declare function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client): Promise<GoogleApis.Auth.Credentials>;
|
|
15
18
|
declare function checkSecrets(profile: string, secretsObject: Secrets, secretsFile: string): true | void;
|
|
19
|
+
declare function getScopesError(scopesFile: string): string;
|
|
16
20
|
declare function getSecretsError(profile: string, secretsFile: string): string;
|
package/dist/lib/secrets.js
CHANGED
|
@@ -33,11 +33,15 @@ const jsonLib_1 = require("./jsonLib");
|
|
|
33
33
|
const logger_1 = require("./logger");
|
|
34
34
|
const paths_1 = require("./paths");
|
|
35
35
|
const secrets_1 = __importDefault(require("./secrets"));
|
|
36
|
-
exports.default = { getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError };
|
|
36
|
+
exports.default = { getScopes, getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
37
37
|
const callbackPort = 6006;
|
|
38
38
|
const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
function getScopes() {
|
|
40
|
+
const scopesFile = (0, paths_1.getScopesFile)();
|
|
41
|
+
const scopes = (0, jsonLib_1.getJSON)(scopesFile, () => (0, logger_1.error)(secrets_1.default.getScopesError(scopesFile)));
|
|
42
|
+
return scopes;
|
|
43
|
+
}
|
|
44
|
+
function getSecrets(profile) {
|
|
41
45
|
const secretsFile = (0, paths_1.getSecretsFile)(profile);
|
|
42
46
|
const secretsObject = (0, jsonLib_1.getJSON)(secretsFile, () => (0, logger_1.error)(secrets_1.default.getSecretsError(profile, secretsFile)));
|
|
43
47
|
secrets_1.default.checkSecrets(profile, secretsObject, secretsFile);
|
|
@@ -50,6 +54,7 @@ async function getCredentials(profile, auth) {
|
|
|
50
54
|
}
|
|
51
55
|
exports.getCredentials = getCredentials;
|
|
52
56
|
async function createCredentials(profile, auth) {
|
|
57
|
+
const scope = secrets_1.default.getScopes();
|
|
53
58
|
return new Promise((resolve) => {
|
|
54
59
|
const authUrl = auth.generateAuthUrl({
|
|
55
60
|
// eslint-disable-next-line camelcase
|
|
@@ -57,7 +62,7 @@ async function createCredentials(profile, auth) {
|
|
|
57
62
|
scope,
|
|
58
63
|
});
|
|
59
64
|
const server = http_1.default.createServer(async (request, response) => {
|
|
60
|
-
response.end('<h1>Please close this page and return to application
|
|
65
|
+
response.end('<h1>Please close this page and return to application. Wait for application to be finished automatically.</h1>');
|
|
61
66
|
if (request.url) {
|
|
62
67
|
const url = new URL(`http://${request.headers.host}${request.url}`);
|
|
63
68
|
const code = url.searchParams.get('code');
|
|
@@ -79,16 +84,45 @@ function checkSecrets(profile, secretsObject, secretsFile) {
|
|
|
79
84
|
}
|
|
80
85
|
(0, logger_1.error)(`Error in credentials file: redirect URI should be ${callbackURI}.\n${secrets_1.default.getSecretsError(profile, secretsFile)}`);
|
|
81
86
|
}
|
|
87
|
+
function getScopesError(scopesFile) {
|
|
88
|
+
return [
|
|
89
|
+
`File ${scopesFile} not found!`,
|
|
90
|
+
`This application had to have pre-defined file ${scopesFile} that will declare needed scopes`,
|
|
91
|
+
].join('\n');
|
|
92
|
+
}
|
|
82
93
|
function getSecretsError(profile, secretsFile) {
|
|
83
94
|
return [
|
|
84
95
|
`File ${secretsFile} not found!`,
|
|
85
|
-
'
|
|
86
|
-
'\tGo to https://console.cloud.google.com/
|
|
87
|
-
'\t
|
|
88
|
-
'\
|
|
89
|
-
|
|
90
|
-
'\
|
|
91
|
-
|
|
96
|
+
'Here is how to obtain it:',
|
|
97
|
+
'\tGo to https://console.cloud.google.com/projectcreate',
|
|
98
|
+
'\t\tChoose project name',
|
|
99
|
+
'\t\tClick "CREATE" and wait for project to become created',
|
|
100
|
+
'\tGo to https://console.cloud.google.com/apis/dashboard',
|
|
101
|
+
'\t\tSelect just created project in the top left dropdown list',
|
|
102
|
+
'\t\tClick "ENABLE APIS AND SERVICES"',
|
|
103
|
+
'\t\t\tClick API you need',
|
|
104
|
+
'\t\t\tClick "ENABLE" and wait for API to become enabled',
|
|
105
|
+
'\t\tClick "Credentials" tab on the left sidebar',
|
|
106
|
+
'\t\t\tClick "CONFIGURE CONSENT SCREEN" on the right',
|
|
107
|
+
'\t\t\t\tChoose "External"',
|
|
108
|
+
'\t\t\t\tClick "CREATE"',
|
|
109
|
+
'\t\t\t\tChoose app name, i.e. "NodeJS"',
|
|
110
|
+
'\t\t\t\tSpecify your email as user support email and as developer contact information on the very bottom',
|
|
111
|
+
'\t\t\t\tClick "Save and continue"',
|
|
112
|
+
'\t\t\tClick "Add or remove scopes"',
|
|
113
|
+
`\t\t\t\tAdd scopes: ${secrets_1.default.getScopes().join(',')}`,
|
|
114
|
+
'\t\t\t\tClick "Save and continue"',
|
|
115
|
+
'\t\t\tClick "Add users"',
|
|
116
|
+
'\t\t\t\tAdd your email',
|
|
117
|
+
'\t\t\t\tClick "Save and continue"',
|
|
118
|
+
'\t\t\tClick "Back to dashboard" on the very bottom',
|
|
119
|
+
'\t\tClick "Credentials" on the left sidebar',
|
|
120
|
+
'\t\t\tClick "CREATE CREDENTIALS" and choose "OAuth client ID"',
|
|
121
|
+
'\t\t\t\tSelect application type "Web application"',
|
|
122
|
+
'\t\t\t\tSpecify app name, i.e. "NodeJS"',
|
|
123
|
+
`\t\t\t\tAdd authorized redirect URI: ${callbackURI}`,
|
|
124
|
+
'\t\t\t\tClick "CREATE"',
|
|
125
|
+
`\t\t\t\tClick "DOWNLOAD JSON" and download credentials to ./secrets/${profile}.json`,
|
|
92
126
|
'Then start this script again',
|
|
93
127
|
].join('\n');
|
|
94
128
|
}
|
package/dist/lib/secrets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAwB;AACxB,qDAAuC;AAGvC,uCAAkD;AAClD,qCAAuC;AACvC,
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,4 +2,4 @@ export * as calendar from './lib/api/calendar';
|
|
|
2
2
|
export * as youtube from './lib/api/youtube';
|
|
3
3
|
export { getItems } from './lib/api/shared';
|
|
4
4
|
export { createProfile, getProfiles } from './lib/profiles';
|
|
5
|
-
export { login } from './lib/auth';
|
|
5
|
+
export { login, getAuth } from './lib/auth';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { google } from 'googleapis';
|
|
2
2
|
import type GoogleApis from 'googleapis';
|
|
3
|
+
import logger from '../logger';
|
|
3
4
|
import profiles from '../profiles';
|
|
4
5
|
import secrets from '../secrets';
|
|
5
6
|
|
|
@@ -10,12 +11,17 @@ jest.mock<typeof auth>('../auth', () => ({
|
|
|
10
11
|
getAuth : jest.fn().mockImplementation(async () => googleAuth),
|
|
11
12
|
}));
|
|
12
13
|
|
|
14
|
+
jest.mock<Partial<typeof logger>>('../logger', () => ({
|
|
15
|
+
info : jest.fn(),
|
|
16
|
+
warn : jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
13
19
|
jest.mock<Partial<typeof profiles>>('../profiles', () => ({
|
|
14
20
|
getProfiles : jest.fn().mockImplementation(() => allProfiles),
|
|
15
21
|
}));
|
|
16
22
|
|
|
17
23
|
jest.mock<Partial<typeof secrets>>('../secrets', () => ({
|
|
18
|
-
getSecrets : jest.fn().mockImplementation(
|
|
24
|
+
getSecrets : jest.fn().mockImplementation(() => secretsObject),
|
|
19
25
|
getCredentials : jest.fn().mockImplementation(async () => credentials),
|
|
20
26
|
}));
|
|
21
27
|
|
|
@@ -60,6 +66,15 @@ describe('src/lib/auth', () => {
|
|
|
60
66
|
});
|
|
61
67
|
});
|
|
62
68
|
|
|
69
|
+
it('should show auth progress for all profiles', async () => {
|
|
70
|
+
await original.login();
|
|
71
|
+
|
|
72
|
+
expect(logger.warn).toBeCalledWith('username1 - logging in...');
|
|
73
|
+
expect(logger.warn).toBeCalledWith('username2 - logging in...');
|
|
74
|
+
expect(logger.info).toBeCalledWith('username1 - logged in successfully');
|
|
75
|
+
expect(logger.info).toBeCalledWith('username2 - logged in successfully');
|
|
76
|
+
});
|
|
77
|
+
|
|
63
78
|
it('should auth only specified profile', async () => {
|
|
64
79
|
await original.login('username1');
|
|
65
80
|
|
|
@@ -7,6 +7,7 @@ jest.mock<typeof paths>('../paths', () => ({
|
|
|
7
7
|
ensureDir : jest.fn().mockImplementation((dirPath) => dirPath),
|
|
8
8
|
ensureFile : jest.fn().mockImplementation((filePath) => filePath),
|
|
9
9
|
getProfilesFile : jest.fn().mockImplementation(() => profilesFile),
|
|
10
|
+
getScopesFile : jest.fn().mockImplementation(() => scopesFile),
|
|
10
11
|
getSecretsFile : jest.fn().mockImplementation(() => secretsFile),
|
|
11
12
|
getCredentialsFile : jest.fn().mockImplementation(() => credentialsFile),
|
|
12
13
|
}));
|
|
@@ -27,6 +28,7 @@ const dirPath = 'dirPath';
|
|
|
27
28
|
const filePath = 'parentDir/filePath';
|
|
28
29
|
|
|
29
30
|
const profilesFile = 'input/profiles.json';
|
|
31
|
+
const scopesFile = 'input/scopes.json';
|
|
30
32
|
const secretsFile = 'secrets/username.json';
|
|
31
33
|
const credentialsFile = 'secrets/username.credentials.json';
|
|
32
34
|
|
|
@@ -98,6 +100,14 @@ describe('src/lib/paths', () => {
|
|
|
98
100
|
});
|
|
99
101
|
});
|
|
100
102
|
|
|
103
|
+
describe('getScopesFile', () => {
|
|
104
|
+
it('should return scopes file', () => {
|
|
105
|
+
const result = original.getScopesFile();
|
|
106
|
+
|
|
107
|
+
expect(result).toEqual(scopesFile);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
101
111
|
describe('getSecretsFile', () => {
|
|
102
112
|
it('should return secrets file', () => {
|
|
103
113
|
const result = original.getSecretsFile(profile);
|
|
@@ -9,11 +9,13 @@ import type { Secrets } from '../../types';
|
|
|
9
9
|
import secrets from '../secrets';
|
|
10
10
|
const original = jest.requireActual('../secrets').default as typeof secrets;
|
|
11
11
|
jest.mock<typeof secrets>('../secrets', () => ({
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
getScopes : jest.fn().mockImplementation(() => scopesJSON),
|
|
13
|
+
getSecrets : jest.fn().mockImplementation(() => secretsJSON),
|
|
14
|
+
getCredentials : jest.fn(),
|
|
14
15
|
createCredentials : jest.fn(),
|
|
15
|
-
checkSecrets
|
|
16
|
-
|
|
16
|
+
checkSecrets : jest.fn(),
|
|
17
|
+
getScopesError : jest.fn().mockImplementation(() => scopesError),
|
|
18
|
+
getSecretsError: jest.fn().mockImplementation(() => secretsError),
|
|
17
19
|
}));
|
|
18
20
|
|
|
19
21
|
jest.mock<Partial<typeof http>>('http', () => ({
|
|
@@ -36,7 +38,7 @@ jest.mock<Partial<typeof colorette>>('colorette', () => ({
|
|
|
36
38
|
}));
|
|
37
39
|
|
|
38
40
|
jest.mock<Partial<typeof jsonLib>>('../jsonLib', () => ({
|
|
39
|
-
getJSON
|
|
41
|
+
getJSON : jest.fn().mockImplementation(() => json),
|
|
40
42
|
getJSONAsync : jest.fn().mockImplementation(async () => json),
|
|
41
43
|
}));
|
|
42
44
|
|
|
@@ -47,23 +49,30 @@ jest.mock<Partial<typeof logger>>('../logger', () => ({
|
|
|
47
49
|
}) as jest.Mock<never, any>,
|
|
48
50
|
}));
|
|
49
51
|
|
|
50
|
-
const profile
|
|
51
|
-
const
|
|
52
|
+
const profile = 'username1';
|
|
53
|
+
const scopesFile = 'input/scopes.json';
|
|
54
|
+
const secretsFile = 'secrets/username1.json';
|
|
52
55
|
const credentialsFile = 'secrets/username1.credentials.json';
|
|
53
56
|
const wrongRedirectURI = 'wrong_redirect_uri';
|
|
54
57
|
|
|
58
|
+
const scopesError = 'scopesError';
|
|
55
59
|
const secretsError = 'secretsError';
|
|
56
60
|
|
|
61
|
+
const scopesJSON: string[] = [
|
|
62
|
+
'https://www.googleapis.com/auth/calendar.calendars.readonly',
|
|
63
|
+
'https://www.googleapis.com/auth/calendar.events.readonly',
|
|
64
|
+
];
|
|
65
|
+
|
|
57
66
|
const secretsJSON: Secrets = {
|
|
58
67
|
web : {
|
|
59
68
|
/* eslint-disable camelcase */
|
|
60
|
-
client_id
|
|
61
|
-
project_id
|
|
62
|
-
auth_uri
|
|
63
|
-
token_uri
|
|
69
|
+
client_id : 'client_id.apps.googleusercontent.com',
|
|
70
|
+
project_id : 'project_id',
|
|
71
|
+
auth_uri : 'https://accounts.google.com/o/oauth2/auth',
|
|
72
|
+
token_uri : 'https://oauth2.googleapis.com/token',
|
|
64
73
|
auth_provider_x509_cert_url : 'https://www.googleapis.com/oauth2/v1/certs',
|
|
65
|
-
client_secret
|
|
66
|
-
redirect_uris
|
|
74
|
+
client_secret : 'client_secret',
|
|
75
|
+
redirect_uris : [ 'http://localhost:6006/oauthcallback' ],
|
|
67
76
|
/* eslint-enable camelcase */
|
|
68
77
|
},
|
|
69
78
|
};
|
|
@@ -74,11 +83,11 @@ const credentialsJSON = {
|
|
|
74
83
|
|
|
75
84
|
let json: object;
|
|
76
85
|
|
|
77
|
-
const code
|
|
86
|
+
const code = 'code';
|
|
78
87
|
const authUrl = 'https://authUrl';
|
|
79
|
-
const auth
|
|
88
|
+
const auth = {
|
|
80
89
|
generateAuthUrl : jest.fn().mockReturnValue(authUrl),
|
|
81
|
-
getToken
|
|
90
|
+
getToken : jest.fn().mockReturnValue({ tokens : credentialsJSON }),
|
|
82
91
|
} as unknown as GoogleApis.Common.OAuth2Client;
|
|
83
92
|
|
|
84
93
|
let request: http.IncomingMessage;
|
|
@@ -100,6 +109,33 @@ const close = jest.fn().mockImplementation(() => {
|
|
|
100
109
|
});
|
|
101
110
|
|
|
102
111
|
describe('src/lib/secrets', () => {
|
|
112
|
+
describe('getScopes', () => {
|
|
113
|
+
const getJSONSpy = jest.spyOn(jsonLib, 'getJSON');
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
json = scopesJSON;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should get json from scopes file', async () => {
|
|
120
|
+
await original.getScopes();
|
|
121
|
+
|
|
122
|
+
expect(getJSONSpy).toBeCalled();
|
|
123
|
+
expect(getJSONSpy.mock.calls[0][0]).toEqual(scopesFile);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should fallback to error', async () => {
|
|
127
|
+
await original.getScopes();
|
|
128
|
+
|
|
129
|
+
expect(getJSONSpy.mock.calls[0][1]).toThrowError(scopesError);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return scopes', async () => {
|
|
133
|
+
const result = await original.getScopes();
|
|
134
|
+
|
|
135
|
+
expect(result).toEqual(scopesJSON);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
103
139
|
describe('getSecrets', () => {
|
|
104
140
|
const getJSONSpy = jest.spyOn(jsonLib, 'getJSON');
|
|
105
141
|
|
|
@@ -172,7 +208,7 @@ describe('src/lib/secrets', () => {
|
|
|
172
208
|
|
|
173
209
|
beforeEach(() => {
|
|
174
210
|
request = {
|
|
175
|
-
url
|
|
211
|
+
url : `/request.url?code=${code}`,
|
|
176
212
|
headers : {
|
|
177
213
|
host : 'localhost:6006',
|
|
178
214
|
},
|
|
@@ -187,7 +223,10 @@ describe('src/lib/secrets', () => {
|
|
|
187
223
|
expect(auth.generateAuthUrl).toBeCalledWith({
|
|
188
224
|
// eslint-disable-next-line camelcase
|
|
189
225
|
access_type : 'offline',
|
|
190
|
-
scope
|
|
226
|
+
scope : [
|
|
227
|
+
'https://www.googleapis.com/auth/calendar.calendars.readonly',
|
|
228
|
+
'https://www.googleapis.com/auth/calendar.events.readonly',
|
|
229
|
+
],
|
|
191
230
|
});
|
|
192
231
|
});
|
|
193
232
|
|
|
@@ -205,7 +244,7 @@ describe('src/lib/secrets', () => {
|
|
|
205
244
|
|
|
206
245
|
await original.createCredentials(profile, auth);
|
|
207
246
|
|
|
208
|
-
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/
|
|
247
|
+
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`);
|
|
209
248
|
});
|
|
210
249
|
|
|
211
250
|
it('should ask to close webpage', async () => {
|
|
@@ -213,7 +252,7 @@ describe('src/lib/secrets', () => {
|
|
|
213
252
|
|
|
214
253
|
await original.createCredentials(profile, auth);
|
|
215
254
|
|
|
216
|
-
expect(response.end).toBeCalledWith('<h1>Please close this page and return to application
|
|
255
|
+
expect(response.end).toBeCalledWith('<h1>Please close this page and return to application. Wait for application to be finished automatically.</h1>');
|
|
217
256
|
});
|
|
218
257
|
|
|
219
258
|
it('should close server if request.url is truthy', async () => {
|
|
@@ -226,9 +265,9 @@ describe('src/lib/secrets', () => {
|
|
|
226
265
|
|
|
227
266
|
it('should only resolve when request.url is truthy', async () => {
|
|
228
267
|
const emptyRequestTime = 100;
|
|
229
|
-
const requestTime
|
|
230
|
-
const emptyRequest
|
|
231
|
-
emptyRequest.url
|
|
268
|
+
const requestTime = 200;
|
|
269
|
+
const emptyRequest = { ...request } as http.IncomingMessage;
|
|
270
|
+
emptyRequest.url = undefined;
|
|
232
271
|
|
|
233
272
|
const before = new Date().getTime();
|
|
234
273
|
willOpen(emptyRequest, emptyRequestTime);
|
|
@@ -245,9 +284,9 @@ describe('src/lib/secrets', () => {
|
|
|
245
284
|
|
|
246
285
|
it('should only resolve when request.url contains no code', async () => {
|
|
247
286
|
const noCodeRequestTime = 100;
|
|
248
|
-
const requestTime
|
|
249
|
-
const noCodeRequest
|
|
250
|
-
noCodeRequest.url
|
|
287
|
+
const requestTime = 200;
|
|
288
|
+
const noCodeRequest = { ...request } as http.IncomingMessage;
|
|
289
|
+
noCodeRequest.url = '/request.url?param=value';
|
|
251
290
|
|
|
252
291
|
const before = new Date().getTime();
|
|
253
292
|
willOpen(noCodeRequest, noCodeRequestTime);
|
|
@@ -279,25 +318,56 @@ describe('src/lib/secrets', () => {
|
|
|
279
318
|
});
|
|
280
319
|
|
|
281
320
|
it('should output error if redirect_uri is incorrect', () => {
|
|
282
|
-
const wrongSecretsJSON
|
|
321
|
+
const wrongSecretsJSON = { ...secretsJSON };
|
|
283
322
|
wrongSecretsJSON.web.redirect_uris[0] = wrongRedirectURI;
|
|
284
|
-
const func
|
|
323
|
+
const func = () => original.checkSecrets(profile, wrongSecretsJSON, secretsFile);
|
|
285
324
|
|
|
286
325
|
expect(func).toThrowError('Error in credentials file: redirect URI should be http://localhost:6006/oauthcallback.\nsecretsError');
|
|
287
326
|
});
|
|
288
327
|
});
|
|
289
328
|
|
|
329
|
+
describe('getScopesError', () => {
|
|
330
|
+
it('should return error message with instructions', () => {
|
|
331
|
+
const result = original.getScopesError(scopesFile);
|
|
332
|
+
expect(result).toEqual(`File ${scopesFile} not found!\n\
|
|
333
|
+
This application had to have pre-defined file ${scopesFile} that will declare needed scopes`);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
290
337
|
describe('getSecretsError', () => {
|
|
291
338
|
it('should return error message with instructions', () => {
|
|
292
339
|
const result = original.getSecretsError(profile, secretsFile);
|
|
293
340
|
expect(result).toEqual(`File ${secretsFile} not found!\n\
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
341
|
+
Here is how to obtain it:\n\
|
|
342
|
+
Go to https://console.cloud.google.com/projectcreate\n\
|
|
343
|
+
Choose project name\n\
|
|
344
|
+
Click "CREATE" and wait for project to become created\n\
|
|
345
|
+
Go to https://console.cloud.google.com/apis/dashboard\n\
|
|
346
|
+
Select just created project in the top left dropdown list\n\
|
|
347
|
+
Click "ENABLE APIS AND SERVICES"\n\
|
|
348
|
+
Click API you need\n\
|
|
349
|
+
Click "ENABLE" and wait for API to become enabled\n\
|
|
350
|
+
Click "Credentials" tab on the left sidebar\n\
|
|
351
|
+
Click "CONFIGURE CONSENT SCREEN" on the right\n\
|
|
352
|
+
Choose "External"\n\
|
|
353
|
+
Click "CREATE"\n\
|
|
354
|
+
Choose app name, i.e. "NodeJS"\n\
|
|
355
|
+
Specify your email as user support email and as developer contact information on the very bottom\n\
|
|
356
|
+
Click "Save and continue"\n\
|
|
357
|
+
Click "Add or remove scopes"\n\
|
|
358
|
+
Add scopes: https://www.googleapis.com/auth/calendar.calendars.readonly,https://www.googleapis.com/auth/calendar.events.readonly\n\
|
|
359
|
+
Click "Save and continue"\n\
|
|
360
|
+
Click "Add users"\n\
|
|
361
|
+
Add your email\n\
|
|
362
|
+
Click "Save and continue"\n\
|
|
363
|
+
Click "Back to dashboard" on the very bottom\n\
|
|
364
|
+
Click "Credentials" on the left sidebar\n\
|
|
365
|
+
Click "CREATE CREDENTIALS" and choose "OAuth client ID"\n\
|
|
366
|
+
Select application type "Web application"\n\
|
|
367
|
+
Specify app name, i.e. "NodeJS"\n\
|
|
368
|
+
Add authorized redirect URI: http://localhost:6006/oauthcallback\n\
|
|
369
|
+
Click "CREATE"\n\
|
|
370
|
+
Click "DOWNLOAD JSON" and download credentials to ./secrets/${profile}.json\n\
|
|
301
371
|
Then start this script again`);
|
|
302
372
|
});
|
|
303
373
|
});
|
|
@@ -36,11 +36,11 @@ const googleAuth = {
|
|
|
36
36
|
setCredentials : jest.fn(),
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const calendars: Array<{ summary?: string, description?: string, hidden?: boolean }> = [
|
|
40
|
-
{ summary : 'calendar 1', description : 'calendar 1 description', hidden : false },
|
|
41
|
-
{ summary : 'calendar 2', description : 'calendar 2 description', hidden : undefined },
|
|
42
|
-
{ summary : 'calendar 3', description : undefined, hidden : true },
|
|
43
|
-
{ summary : 'calendar 4', description : undefined, hidden : undefined },
|
|
39
|
+
const calendars: Array<{ id?: string | null | undefined, summary?: string, description?: string, hidden?: boolean }> = [
|
|
40
|
+
{ id : 'id1', summary : 'calendar 1', description : 'calendar 1 description', hidden : false },
|
|
41
|
+
{ id : 'id2', summary : 'calendar 2', description : 'calendar 2 description', hidden : undefined },
|
|
42
|
+
{ id : null, summary : 'calendar 3', description : undefined, hidden : true },
|
|
43
|
+
{ id : 'id4', summary : 'calendar 4', description : undefined, hidden : undefined },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
const calendarsResponse = [
|
|
@@ -49,11 +49,11 @@ const calendarsResponse = [
|
|
|
49
49
|
[ calendars[2], calendars[3] ],
|
|
50
50
|
];
|
|
51
51
|
|
|
52
|
-
const events: Array<{ summary?: string, source?: { url?: string, title?: string} }> = [
|
|
53
|
-
{ summary : 'event 1', source : { title : 'source 1', url : 'https://example.com' } },
|
|
54
|
-
{ summary : 'event 2', source : { title : 'source 2', url : undefined } },
|
|
55
|
-
{ summary : 'event 3', source : { title : undefined, url : undefined } },
|
|
56
|
-
{ summary : 'event 4', source : undefined },
|
|
52
|
+
const events: Array<{ id?: string | null | undefined, summary?: string, source?: { url?: string, title?: string} }> = [
|
|
53
|
+
{ id : 'id1', summary : 'event 1', source : { title : 'source 1', url : 'https://example.com' } },
|
|
54
|
+
{ id : null, summary : 'event 2', source : { title : 'source 2', url : undefined } },
|
|
55
|
+
{ id : 'id3', summary : 'event 3', source : { title : undefined, url : undefined } },
|
|
56
|
+
{ id : 'id4', summary : 'event 4', source : undefined },
|
|
57
57
|
];
|
|
58
58
|
|
|
59
59
|
const eventsResponse = [
|
|
@@ -36,11 +36,11 @@ const googleAuth = {
|
|
|
36
36
|
setCredentials : jest.fn(),
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const playlistItems: Array<{ snippet?: { title?: string, resourceId?: { videoId?: string } } }> = [
|
|
40
|
-
{ snippet : { title : 'video1', resourceId : { videoId : 'video1Id' } } },
|
|
41
|
-
{ snippet : { title : 'video2', resourceId : { videoId : undefined } } },
|
|
42
|
-
{ snippet : { title : undefined, resourceId : undefined } },
|
|
43
|
-
{ snippet : undefined },
|
|
39
|
+
const playlistItems: Array<{ id?: string | null | undefined, snippet?: { title?: string, resourceId?: { videoId?: string } } }> = [
|
|
40
|
+
{ id : 'id1', snippet : { title : 'video1', resourceId : { videoId : 'video1Id' } } },
|
|
41
|
+
{ id : null, snippet : { title : 'video2', resourceId : { videoId : undefined } } },
|
|
42
|
+
{ id : 'id3', snippet : { title : undefined, resourceId : undefined } },
|
|
43
|
+
{ id : 'id4', snippet : undefined },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
const playlistItemsResponse = [
|
package/src/lib/api/calendar.ts
CHANGED
|
@@ -16,12 +16,12 @@ async function getAPI(profile: string): Promise<GoogleApis.calendar_v3.Calendar>
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async function getCalendars(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Calendarlist$List): Promise<GoogleApis.calendar_v3.Schema$
|
|
19
|
+
async function getCalendars(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Calendarlist$List): Promise<GoogleApis.calendar_v3.Schema$CalendarListEntry[]> {
|
|
20
20
|
const api = await calendar.getAPI(profile);
|
|
21
21
|
return getItems(api.calendarList, args);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
async function getEvents(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Events$List): Promise<GoogleApis.calendar_v3.Schema$
|
|
24
|
+
async function getEvents(profile: string, args: GoogleApis.calendar_v3.Params$Resource$Events$List): Promise<GoogleApis.calendar_v3.Schema$Event[]> {
|
|
25
25
|
const api = await calendar.getAPI(profile);
|
|
26
26
|
return getItems(api.events, args);
|
|
27
27
|
}
|
package/src/lib/auth.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { google } from 'googleapis';
|
|
2
2
|
import type GoogleApis from 'googleapis';
|
|
3
|
+
import { info, warn } from './logger';
|
|
3
4
|
import { getProfiles } from './profiles';
|
|
4
5
|
import { getCredentials, getSecrets } from './secrets';
|
|
5
6
|
|
|
@@ -12,12 +13,14 @@ async function login(profile?: string): Promise<void> {
|
|
|
12
13
|
const profiles = getProfiles().filter((p) => !profile || p === profile);
|
|
13
14
|
|
|
14
15
|
for (const profile of profiles) {
|
|
16
|
+
warn(`${profile} - logging in...`);
|
|
15
17
|
await auth.getAuth(profile);
|
|
18
|
+
info(`${profile} - logged in successfully`);
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
async function getAuth(profile: string): Promise<GoogleApis.Common.OAuth2Client> {
|
|
20
|
-
const secrets =
|
|
23
|
+
const secrets = getSecrets(profile);
|
|
21
24
|
|
|
22
25
|
const googleAuth = new google.auth.OAuth2(
|
|
23
26
|
secrets.web.client_id,
|
package/src/lib/paths.ts
CHANGED
|
@@ -2,8 +2,8 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import paths from './paths';
|
|
4
4
|
|
|
5
|
-
export { ensureDir, ensureFile, getProfilesFile, getSecretsFile, getCredentialsFile };
|
|
6
|
-
export default { ensureDir, ensureFile, getProfilesFile, getSecretsFile, getCredentialsFile };
|
|
5
|
+
export { ensureDir, ensureFile, getProfilesFile, getScopesFile, getSecretsFile, getCredentialsFile };
|
|
6
|
+
export default { ensureDir, ensureFile, getProfilesFile, getScopesFile, getSecretsFile, getCredentialsFile };
|
|
7
7
|
|
|
8
8
|
const dirPaths = {
|
|
9
9
|
input : 'input',
|
|
@@ -30,6 +30,10 @@ function getProfilesFile() {
|
|
|
30
30
|
return path.join(dirPaths.input, 'profiles.json');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function getScopesFile() {
|
|
34
|
+
return path.join(dirPaths.input, 'scopes.json');
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
function getSecretsFile(profile: string) {
|
|
34
38
|
return path.join(dirPaths.secrets, `${profile}.json`);
|
|
35
39
|
}
|
package/src/lib/secrets.ts
CHANGED
|
@@ -4,18 +4,23 @@ import type GoogleApis from 'googleapis';
|
|
|
4
4
|
import type { Secrets } from '../types';
|
|
5
5
|
import { getJSON, getJSONAsync } from './jsonLib';
|
|
6
6
|
import { info, error } from './logger';
|
|
7
|
-
import { getSecretsFile, getCredentialsFile } from './paths';
|
|
7
|
+
import { getScopesFile, getSecretsFile, getCredentialsFile } from './paths';
|
|
8
8
|
|
|
9
9
|
import secrets from './secrets';
|
|
10
10
|
|
|
11
11
|
export { getSecrets, getCredentials };
|
|
12
|
-
export default { getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError };
|
|
12
|
+
export default { getScopes, getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
13
13
|
|
|
14
14
|
const callbackPort = 6006;
|
|
15
15
|
const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
|
|
16
|
-
const scope = [ 'https://www.googleapis.com/auth/youtube.readonly' ];
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
function getScopes(): string[] {
|
|
18
|
+
const scopesFile = getScopesFile();
|
|
19
|
+
const scopes = getJSON<string[]>(scopesFile, () => error(secrets.getScopesError(scopesFile)) as never);
|
|
20
|
+
return scopes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getSecrets(profile: string): Secrets {
|
|
19
24
|
const secretsFile = getSecretsFile(profile);
|
|
20
25
|
const secretsObject = getJSON<Secrets>(secretsFile, () => error(secrets.getSecretsError(profile, secretsFile)) as never);
|
|
21
26
|
secrets.checkSecrets(profile, secretsObject, secretsFile);
|
|
@@ -28,6 +33,8 @@ async function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Cli
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client): Promise<GoogleApis.Auth.Credentials> {
|
|
36
|
+
const scope = secrets.getScopes();
|
|
37
|
+
|
|
31
38
|
return new Promise((resolve) => {
|
|
32
39
|
const authUrl = auth.generateAuthUrl({
|
|
33
40
|
// eslint-disable-next-line camelcase
|
|
@@ -36,7 +43,7 @@ async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Cl
|
|
|
36
43
|
});
|
|
37
44
|
|
|
38
45
|
const server = http.createServer(async (request, response) => {
|
|
39
|
-
response.end('<h1>Please close this page and return to application
|
|
46
|
+
response.end('<h1>Please close this page and return to application. Wait for application to be finished automatically.</h1>');
|
|
40
47
|
|
|
41
48
|
if (request.url) {
|
|
42
49
|
const url = new URL(`http://${request.headers.host}${request.url}`);
|
|
@@ -64,16 +71,46 @@ function checkSecrets(profile: string, secretsObject: Secrets, secretsFile: stri
|
|
|
64
71
|
error(`Error in credentials file: redirect URI should be ${callbackURI}.\n${secrets.getSecretsError(profile, secretsFile)}`);
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
function getScopesError(scopesFile: string) {
|
|
75
|
+
return [
|
|
76
|
+
`File ${scopesFile} not found!`,
|
|
77
|
+
`This application had to have pre-defined file ${scopesFile} that will declare needed scopes`,
|
|
78
|
+
].join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
function getSecretsError(profile: string, secretsFile: string) {
|
|
68
82
|
return [
|
|
69
83
|
`File ${secretsFile} not found!`,
|
|
70
|
-
'
|
|
71
|
-
'\tGo to https://console.cloud.google.com/
|
|
72
|
-
'\t
|
|
73
|
-
'\
|
|
74
|
-
|
|
75
|
-
'\
|
|
76
|
-
|
|
84
|
+
'Here is how to obtain it:',
|
|
85
|
+
'\tGo to https://console.cloud.google.com/projectcreate',
|
|
86
|
+
'\t\tChoose project name',
|
|
87
|
+
'\t\tClick "CREATE" and wait for project to become created',
|
|
88
|
+
'\tGo to https://console.cloud.google.com/apis/dashboard',
|
|
89
|
+
'\t\tSelect just created project in the top left dropdown list',
|
|
90
|
+
'\t\tClick "ENABLE APIS AND SERVICES"',
|
|
91
|
+
'\t\t\tClick API you need',
|
|
92
|
+
'\t\t\tClick "ENABLE" and wait for API to become enabled',
|
|
93
|
+
'\t\tClick "Credentials" tab on the left sidebar',
|
|
94
|
+
'\t\t\tClick "CONFIGURE CONSENT SCREEN" on the right',
|
|
95
|
+
'\t\t\t\tChoose "External"',
|
|
96
|
+
'\t\t\t\tClick "CREATE"',
|
|
97
|
+
'\t\t\t\tChoose app name, i.e. "NodeJS"',
|
|
98
|
+
'\t\t\t\tSpecify your email as user support email and as developer contact information on the very bottom',
|
|
99
|
+
'\t\t\t\tClick "Save and continue"',
|
|
100
|
+
'\t\t\tClick "Add or remove scopes"',
|
|
101
|
+
`\t\t\t\tAdd scopes: ${secrets.getScopes().join(',')}`,
|
|
102
|
+
'\t\t\t\tClick "Save and continue"',
|
|
103
|
+
'\t\t\tClick "Add users"',
|
|
104
|
+
'\t\t\t\tAdd your email',
|
|
105
|
+
'\t\t\t\tClick "Save and continue"',
|
|
106
|
+
'\t\t\tClick "Back to dashboard" on the very bottom',
|
|
107
|
+
'\t\tClick "Credentials" on the left sidebar',
|
|
108
|
+
'\t\t\tClick "CREATE CREDENTIALS" and choose "OAuth client ID"',
|
|
109
|
+
'\t\t\t\tSelect application type "Web application"',
|
|
110
|
+
'\t\t\t\tSpecify app name, i.e. "NodeJS"',
|
|
111
|
+
`\t\t\t\tAdd authorized redirect URI: ${callbackURI}`,
|
|
112
|
+
'\t\t\t\tClick "CREATE"',
|
|
113
|
+
`\t\t\t\tClick "DOWNLOAD JSON" and download credentials to ./secrets/${profile}.json`,
|
|
77
114
|
'Then start this script again',
|
|
78
115
|
].join('\n');
|
|
79
116
|
}
|