@anmiles/google-api-wrapper 18.0.2 → 19.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.
Files changed (108) hide show
  1. package/.nycrc.json +27 -0
  2. package/CHANGELOG.md +32 -21
  3. package/README.md +1 -1
  4. package/cspell.json +22 -0
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4 -3
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/api.d.ts +5 -11
  10. package/dist/lib/api.d.ts.map +1 -1
  11. package/dist/lib/api.js +5 -6
  12. package/dist/lib/api.js.map +1 -1
  13. package/dist/lib/auth.d.ts +2 -9
  14. package/dist/lib/auth.d.ts.map +1 -1
  15. package/dist/lib/auth.js +3 -23
  16. package/dist/lib/auth.js.map +1 -1
  17. package/dist/lib/credentials/generator.d.ts +4 -0
  18. package/dist/lib/credentials/generator.d.ts.map +1 -0
  19. package/dist/lib/credentials/generator.js +60 -0
  20. package/dist/lib/credentials/generator.js.map +1 -0
  21. package/dist/lib/credentials/index.d.ts +5 -0
  22. package/dist/lib/credentials/index.d.ts.map +1 -0
  23. package/dist/lib/credentials/index.js +41 -0
  24. package/dist/lib/credentials/index.js.map +1 -0
  25. package/dist/lib/credentials/validator.d.ts +6 -0
  26. package/dist/lib/credentials/validator.d.ts.map +1 -0
  27. package/dist/lib/credentials/validator.js +21 -0
  28. package/dist/lib/credentials/validator.js.map +1 -0
  29. package/dist/lib/login.d.ts +3 -0
  30. package/dist/lib/login.d.ts.map +1 -0
  31. package/dist/lib/login.js +19 -0
  32. package/dist/lib/login.js.map +1 -0
  33. package/dist/lib/profiles.d.ts +4 -12
  34. package/dist/lib/profiles.d.ts.map +1 -1
  35. package/dist/lib/profiles.js +8 -11
  36. package/dist/lib/profiles.js.map +1 -1
  37. package/dist/lib/renderer.d.ts +7 -23
  38. package/dist/lib/renderer.d.ts.map +1 -1
  39. package/dist/lib/renderer.js +26 -20
  40. package/dist/lib/renderer.js.map +1 -1
  41. package/dist/lib/scopes.d.ts +2 -0
  42. package/dist/lib/scopes.d.ts.map +1 -0
  43. package/dist/lib/scopes.js +22 -0
  44. package/dist/lib/scopes.js.map +1 -0
  45. package/dist/lib/secrets.d.ts +2 -28
  46. package/dist/lib/secrets.d.ts.map +1 -1
  47. package/dist/lib/secrets.js +11 -112
  48. package/dist/lib/secrets.js.map +1 -1
  49. package/dist/lib/utils/paths.d.ts +7 -0
  50. package/dist/lib/utils/paths.d.ts.map +1 -0
  51. package/dist/lib/{paths.js → utils/paths.js} +6 -8
  52. package/dist/lib/utils/paths.js.map +1 -0
  53. package/dist/templates/auth.html +1 -1
  54. package/dist/templates/index.html +17 -0
  55. package/dist/templates/page.html +1 -2
  56. package/dist/templates/script.html +0 -0
  57. package/{src/templates/css.html → dist/templates/style.html} +2 -2
  58. package/dist/types/options.d.ts +2 -3
  59. package/dist/types/options.d.ts.map +1 -1
  60. package/dist/types/secrets.d.ts +1 -2
  61. package/dist/types/secrets.d.ts.map +1 -1
  62. package/eslint.config.mts +43 -0
  63. package/jest.config.js +9 -9
  64. package/package.json +40 -30
  65. package/src/index.ts +3 -2
  66. package/src/lib/__tests__/__snapshots__/renderer.test.ts.snap +273 -0
  67. package/src/lib/__tests__/__snapshots__/scopes.test.ts.snap +6 -0
  68. package/src/lib/__tests__/__snapshots__/secrets.test.ts.snap +38 -0
  69. package/src/lib/__tests__/api.test.ts +72 -74
  70. package/src/lib/__tests__/auth.test.ts +38 -114
  71. package/src/lib/__tests__/login.test.ts +71 -0
  72. package/src/lib/__tests__/profiles.test.ts +50 -93
  73. package/src/lib/__tests__/renderer.test.ts +16 -88
  74. package/src/lib/__tests__/scopes.test.ts +41 -0
  75. package/src/lib/__tests__/secrets.test.ts +47 -541
  76. package/src/lib/api.ts +19 -21
  77. package/src/lib/auth.ts +5 -25
  78. package/src/lib/credentials/__tests__/generator.test.ts +249 -0
  79. package/src/lib/credentials/__tests__/index.test.ts +213 -0
  80. package/src/lib/credentials/__tests__/validator.test.ts +34 -0
  81. package/src/lib/credentials/generator.ts +70 -0
  82. package/src/lib/credentials/index.ts +49 -0
  83. package/src/lib/credentials/validator.ts +24 -0
  84. package/src/lib/login.ts +22 -0
  85. package/src/lib/profiles.ts +9 -12
  86. package/src/lib/renderer.ts +31 -23
  87. package/src/lib/scopes.ts +18 -0
  88. package/src/lib/secrets.ts +17 -139
  89. package/src/lib/utils/paths.ts +30 -0
  90. package/src/templates/auth.html +1 -1
  91. package/src/templates/index.html +17 -0
  92. package/src/templates/page.html +1 -2
  93. package/src/templates/script.html +0 -0
  94. package/{dist/templates/css.html → src/templates/style.html} +2 -2
  95. package/src/types/options.ts +5 -7
  96. package/src/types/secrets.ts +8 -10
  97. package/tsconfig.build.json +6 -0
  98. package/tsconfig.json +0 -5
  99. package/tsconfig.test.json +1 -1
  100. package/.eslintignore +0 -2
  101. package/.eslintrc.js +0 -30
  102. package/.vscode/settings.json +0 -9
  103. package/coverage.config.js +0 -8
  104. package/dist/lib/paths.d.ts +0 -16
  105. package/dist/lib/paths.d.ts.map +0 -1
  106. package/dist/lib/paths.js.map +0 -1
  107. package/src/lib/__tests__/paths.test.ts +0 -77
  108. package/src/lib/paths.ts +0 -32
@@ -0,0 +1,70 @@
1
+ import http from 'http';
2
+
3
+ import { warn } from '@anmiles/logger';
4
+ import type GoogleApis from 'googleapis';
5
+ import { open } from 'out-url';
6
+ import enableDestroy from 'server-destroy';
7
+
8
+ import type { AuthOptions } from '../../types/options';
9
+ import { renderAuth, renderDone } from '../renderer';
10
+ import { getScopes } from '../scopes';
11
+
12
+ const port = 6006;
13
+ const host = `localhost:${port}`;
14
+ const startURI = `http://${host}/`;
15
+ const serverRetryInterval = 1000;
16
+
17
+ export async function generateCredentials(
18
+ profile: string,
19
+ auth: GoogleApis.Auth.OAuth2Client,
20
+ options?: AuthOptions,
21
+ prompt?: GoogleApis.Auth.GenerateAuthUrlOpts['prompt'],
22
+ ): Promise<GoogleApis.Auth.Credentials> {
23
+ const scope = options?.scopes ?? getScopes();
24
+
25
+ return new Promise((resolve) => {
26
+ const authUrl = auth.generateAuthUrl({
27
+ access_type: 'offline',
28
+ prompt,
29
+ scope,
30
+ });
31
+
32
+ const server = http.createServer();
33
+ enableDestroy(server);
34
+
35
+ server.on('request', (request, response) => {
36
+ if (!request.url) {
37
+ response.end('');
38
+ return;
39
+ }
40
+
41
+ const url = new URL(`http://${request.headers.host}${request.url}`);
42
+ const code = url.searchParams.get('code');
43
+
44
+ if (!code) {
45
+ response.end(renderAuth({ profile, authUrl, scope }));
46
+ return;
47
+ }
48
+
49
+ response.end(renderDone({ profile }));
50
+ server.destroy();
51
+
52
+ void (async () => {
53
+ const { tokens } = await auth.getToken(code);
54
+ resolve(tokens);
55
+ })();
56
+ });
57
+ server.on('error', (error: NodeJS.ErrnoException) => {
58
+ if (error.code === 'EADDRINUSE') {
59
+ setTimeout(() => server.listen(port), serverRetryInterval);
60
+ } else {
61
+ throw error;
62
+ }
63
+ });
64
+ server.once('listening', () => {
65
+ warn('Please check your browser for further actions');
66
+ void open(startURI);
67
+ });
68
+ server.listen(port);
69
+ });
70
+ }
@@ -0,0 +1,49 @@
1
+ import fs from 'fs';
2
+
3
+ import type GoogleApis from 'googleapis';
4
+
5
+ import type { AuthOptions } from '../../types/options';
6
+ import { getCredentialsFile } from '../utils/paths';
7
+
8
+ import { generateCredentials } from './generator';
9
+ import { validateCredentials } from './validator';
10
+
11
+ export async function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials> {
12
+ const credentialsFile = getCredentialsFile(profile);
13
+
14
+ if (options?.temporary) {
15
+ const credentials = await generateCredentials(profile, auth, options);
16
+ const validationResult = await validateCredentials(credentials);
17
+
18
+ if (!validationResult.isValid) {
19
+ throw new Error(validationResult.validationError);
20
+ }
21
+
22
+ return credentials;
23
+ }
24
+
25
+ return fs.getJSONAsync(credentialsFile, async () => {
26
+ const refreshToken = fs.existsSync(credentialsFile)
27
+ ? fs.readJSON<GoogleApis.Auth.Credentials>(credentialsFile).refresh_token
28
+ : undefined;
29
+
30
+ const credentials = await generateCredentials(profile, auth, options,
31
+ refreshToken
32
+ ? undefined
33
+ : 'consent',
34
+ );
35
+
36
+ return {
37
+ refresh_token: refreshToken,
38
+ ...credentials,
39
+ };
40
+ }, validateCredentials);
41
+ }
42
+
43
+ export function deleteCredentials(profile: string): void {
44
+ const credentialsFile = getCredentialsFile(profile);
45
+
46
+ if (fs.existsSync(credentialsFile)) {
47
+ fs.rmSync(credentialsFile);
48
+ }
49
+ }
@@ -0,0 +1,24 @@
1
+ import type GoogleApis from 'googleapis';
2
+
3
+ const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
4
+
5
+ // eslint-disable-next-line @typescript-eslint/require-await -- pass sync function into async context
6
+ export async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<{ isValid: boolean; validationError?: string }> {
7
+ if (!credentials.access_token) {
8
+ return { isValid: false, validationError: 'Credentials does not have access_token' };
9
+ }
10
+
11
+ if (!credentials.refresh_token) {
12
+ return { isValid: false, validationError: 'Credentials does not have refresh_token' };
13
+ }
14
+
15
+ if (!credentials.expiry_date) {
16
+ return { isValid: false, validationError: 'Credentials does not have expiry_date' };
17
+ }
18
+
19
+ if (new Date().getTime() - credentials.expiry_date >= tokenExpiration) {
20
+ return { isValid: false, validationError: 'Credentials expired' };
21
+ }
22
+
23
+ return { isValid: true };
24
+ }
@@ -0,0 +1,22 @@
1
+ import { info, warn } from '@anmiles/logger';
2
+
3
+ import type { AuthOptions, CommonOptions } from '../types/options';
4
+
5
+ import { getAuth } from './auth';
6
+ import { getProfiles } from './profiles';
7
+
8
+ export async function login(profile?: string, options?: AuthOptions & CommonOptions): Promise<void> {
9
+ const profiles = getProfiles().filter((p) => !profile || p === profile);
10
+
11
+ for (const profile of profiles) {
12
+ if (!options?.hideProgress) {
13
+ warn(`${profile} - logging in...`);
14
+ }
15
+
16
+ await getAuth(profile, options);
17
+
18
+ if (!options?.hideProgress) {
19
+ info(`${profile} - logged in successfully`);
20
+ }
21
+ }
22
+ }
@@ -1,36 +1,36 @@
1
1
  import fs from 'fs';
2
+
2
3
  import '@anmiles/prototypes';
3
- import { getProfilesFile } from './paths';
4
4
 
5
- import profiles from './profiles';
5
+ import { getProfilesFile } from './utils/paths';
6
6
 
7
- function getProfiles(): string[] {
7
+ export function getProfiles(): string[] {
8
8
  const profilesFile = getProfilesFile();
9
9
  return fs.getJSON(profilesFile, () => []);
10
10
  }
11
11
 
12
- function setProfiles(profiles: string[]): void {
12
+ export function setProfiles(profiles: string[]): void {
13
13
  const profilesFile = getProfilesFile();
14
14
  fs.writeJSON(profilesFile, profiles);
15
15
  }
16
16
 
17
- function createProfile(profile?: string): void {
17
+ export function createProfile(profile?: string): void {
18
18
  if (!profile) {
19
19
  throw new Error('Usage: `npm run create <profile>` where `profile` - is any profile name you want');
20
20
  }
21
21
 
22
- const existingProfiles = profiles.getProfiles();
22
+ const existingProfiles = getProfiles();
23
23
 
24
24
  if (existingProfiles.includes(profile)) {
25
25
  return;
26
26
  }
27
27
 
28
28
  existingProfiles.push(profile);
29
- profiles.setProfiles(existingProfiles);
29
+ setProfiles(existingProfiles);
30
30
  }
31
31
 
32
- function filterProfiles(profile?: string): string[] {
33
- const existingProfiles = profiles.getProfiles();
32
+ export function filterProfiles(profile?: string): string[] {
33
+ const existingProfiles = getProfiles();
34
34
 
35
35
  if (existingProfiles.length === 0) {
36
36
  throw new Error('Please `npm run create` at least one profile');
@@ -46,6 +46,3 @@ function filterProfiles(profile?: string): string[] {
46
46
 
47
47
  throw new Error(`Profile '${profile}' does not exist`);
48
48
  }
49
-
50
- export { getProfiles, setProfiles, createProfile, filterProfiles };
51
- export default { getProfiles, setProfiles, createProfile, filterProfiles };
@@ -1,45 +1,56 @@
1
1
  import fs from 'fs';
2
- import renderer from './renderer';
3
- import { getTemplateFile } from './paths';
4
2
 
5
- const templates = {
6
- page : [ 'css', 'content' ] as const,
7
- css : [ ] as const,
8
- auth : [ 'profile', 'authUrl', 'scopesList' ],
3
+ import { getTemplateFile } from './utils/paths';
4
+
5
+ export const templates = {
6
+ index : [ 'style', 'page', 'script' ] as const,
7
+ page : [ 'content' ] as const,
8
+ style : [ ] as const,
9
+ script: [ ] as const,
10
+ auth : [ 'profile', 'authUrl', 'scopesList' ] as const,
9
11
  scope : [ 'type', 'title', 'name' ] as const,
10
12
  done : [ 'profile' ] as const,
11
13
  } as const;
12
14
 
13
15
  type TemplateName = keyof typeof templates;
14
16
 
15
- const allHTML = {} as Record<TemplateName, string>;
17
+ const allHTML = {} as Record<TemplateName, string>; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
16
18
 
17
- function renderAuth({ profile, authUrl, scope }: { profile : string; authUrl : string; scope : string[] }): string {
19
+ export function renderAuth({ profile, authUrl, scope }: { profile: string; authUrl: string; scope: string[] }): string {
18
20
  const scopesList = scope.map((s) => render('scope', {
19
- name : s.split('/').pop()!,
20
- title : s.endsWith('.readonly') ? 'Readonly (cannot change or delete your data)' : 'Writable (can change or delete your data)',
21
- type : s.endsWith('.readonly') ? 'readonly' : '',
21
+ name : s.split('/').pop()!,
22
+ title: s.endsWith('.readonly') ? 'Readonly (cannot change or delete your data)' : 'Writable (can change or delete your data)',
23
+ type : s.endsWith('.readonly') ? 'readonly' : '',
22
24
  })).join('\n');
23
25
 
24
- const css = render('css', {});
26
+ const style = render('style', {});
27
+ const script = render('script', {});
25
28
  const content = render('auth', { profile, authUrl, scopesList });
26
- return render('page', { css, content });
29
+ const page = render('page', { content });
30
+ return render('index', { style, page, script });
27
31
  }
28
32
 
29
- function renderDone({ profile }: { profile : string }): string {
30
- const css = render('css', {});
33
+ export function renderDone({ profile }: { profile: string }): string {
34
+ const style = render('style', {});
35
+ const script = render('script', {});
31
36
  const content = render('done', { profile });
32
- return render('page', { css, content });
37
+ const page = render('page', { content });
38
+ return render('index', { style, page, script });
33
39
  }
34
40
 
35
41
  // TODO: Use react
36
42
  function render<T extends TemplateName>(templateName: T, values: Record<typeof templates[T][number], string | undefined>): string {
37
- let html = renderer.getTemplate(templateName);
43
+ let html = getTemplate(templateName);
38
44
  const allValues = values as Record<typeof templates[TemplateName][number], string | undefined>;
39
45
 
40
46
  for (const variable of templates[templateName]) {
41
- const value = allValues[variable] ?? '';
42
- html = html.replace(`\${${variable}}`, value);
47
+ const value = allValues[variable];
48
+
49
+ if (typeof value === 'undefined') {
50
+ throw new Error(`Missing required value '${variable}' while rendering template '${templateName}'`);
51
+ }
52
+
53
+ html = html.replaceAll(`\${${variable}}`, value);
43
54
  }
44
55
 
45
56
  return html;
@@ -48,12 +59,9 @@ function render<T extends TemplateName>(templateName: T, values: Record<typeof t
48
59
  function getTemplate(templateName: TemplateName): string {
49
60
  if (!(templateName in allHTML)) {
50
61
  const file = getTemplateFile(templateName);
51
- const template = fs.readFileSync(file).toString();
62
+ const template = fs.readFileSync(file).toString().trim();
52
63
  allHTML[templateName] = template;
53
64
  }
54
65
 
55
66
  return allHTML[templateName];
56
67
  }
57
-
58
- export { templates, renderAuth, renderDone };
59
- export default { templates, render, getTemplate, renderAuth, renderDone };
@@ -0,0 +1,18 @@
1
+ import fs from 'fs';
2
+
3
+ import { getScopesFile } from './utils/paths';
4
+
5
+ export function getScopes(): string[] {
6
+ const scopesFile = getScopesFile();
7
+ const scopes = fs.getJSON<string[]>(scopesFile, () => {
8
+ throw new Error(getScopesError(scopesFile));
9
+ });
10
+ return scopes;
11
+ }
12
+
13
+ function getScopesError(scopesFile: string): string {
14
+ return [
15
+ `File ${scopesFile} not found!`,
16
+ `This application had to have pre-defined file ${scopesFile} that will declare needed scopes`,
17
+ ].join('\n');
18
+ }
@@ -1,149 +1,30 @@
1
1
  import fs from 'fs';
2
- import http from 'http';
3
- import { open } from 'out-url';
4
- import enableDestroy from 'server-destroy';
5
- import type GoogleApis from 'googleapis';
6
- import { warn } from '@anmiles/logger';
7
- import type { Secrets } from '../types/secrets';
8
- import type { AuthOptions } from '../types/options';
9
- import '@anmiles/prototypes';
10
- import { getScopesFile, getSecretsFile, getCredentialsFile } from './paths';
11
- import { renderAuth, renderDone } from './renderer';
12
-
13
- import secrets from './secrets';
14
-
15
- const port = 6006;
16
- const host = `localhost:${port}`;
17
- const startURI = `http://${host}/`;
18
- const callbackURI = `http://${host}/oauthcallback`;
19
- const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
20
- const serverRetryInterval = 1000;
21
-
22
- function getScopes(): string[] {
23
- const scopesFile = getScopesFile();
24
- const scopes = fs.getJSON<string[]>(scopesFile, () => {
25
- throw new Error(secrets.getScopesError(scopesFile));
26
- });
27
- return scopes;
28
- }
29
-
30
- function getSecrets(profile: string): Secrets {
31
- const secretsFile = getSecretsFile(profile);
32
- const secretsObject = fs.getJSON<Secrets>(secretsFile, () => {
33
- throw new Error(secrets.getSecretsError(profile, secretsFile));
34
- });
35
- secrets.checkSecrets(profile, secretsObject, secretsFile);
36
- return secretsObject;
37
- }
38
-
39
- async function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials> {
40
- const credentialsFile = getCredentialsFile(profile);
41
-
42
- if (options?.temporary) {
43
- return secrets.createCredentials(profile, auth, options);
44
- }
45
-
46
- return fs.getJSONAsync(credentialsFile, async () => {
47
- const refreshToken = fs.existsSync(credentialsFile) ? fs.readJSON<GoogleApis.Auth.Credentials>(credentialsFile).refresh_token : undefined;
48
- const credentials = await secrets.createCredentials(profile, auth, options, refreshToken ? undefined : 'consent');
49
- return { refresh_token : refreshToken, ...credentials };
50
- }, secrets.validateCredentials);
51
- }
52
-
53
- // eslint-disable-next-line @typescript-eslint/require-await -- pass sync function into async context
54
- async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<{ isValid : boolean; validationError? : string }> {
55
- if (!credentials.access_token) {
56
- return { isValid : false, validationError : 'Credentials does not have access_token' };
57
- }
58
-
59
- if (!credentials.refresh_token) {
60
- return { isValid : false, validationError : 'Credentials does not have refresh_token' };
61
- }
62
-
63
- if (!credentials.expiry_date) {
64
- return { isValid : false, validationError : 'Credentials does not have expiry_date' };
65
- }
66
-
67
- if (new Date().getTime() - credentials.expiry_date >= tokenExpiration) {
68
- return { isValid : false, validationError : 'Credentials expired' };
69
- }
70
-
71
- return { isValid : true };
72
- }
73
-
74
- async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions, prompt?: GoogleApis.Auth.GenerateAuthUrlOpts['prompt']): Promise<GoogleApis.Auth.Credentials> {
75
- const scope = options?.scopes ?? secrets.getScopes();
76
-
77
- return new Promise((resolve) => {
78
- const authUrl = auth.generateAuthUrl({
79
- access_type : 'offline',
80
- prompt,
81
- scope,
82
- });
83
-
84
- const server = http.createServer();
85
- enableDestroy(server);
86
-
87
- server.on('request', (request, response) => {
88
- if (!request.url) {
89
- response.end('');
90
- return;
91
- }
92
2
 
93
- const url = new URL(`http://${request.headers.host}${request.url}`);
94
- const code = url.searchParams.get('code');
95
-
96
- if (!code) {
97
- response.end(renderAuth({ profile, authUrl, scope }));
98
- return;
99
- }
100
-
101
- response.end(renderDone({ profile }));
102
- server.destroy();
103
-
104
- void (async () => {
105
- const { tokens } = await auth.getToken(code);
106
- resolve(tokens);
107
- })();
108
- });
109
-
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
- void open(startURI);
121
- });
3
+ import '@anmiles/prototypes';
122
4
 
123
- server.listen(port);
124
- });
125
- }
5
+ import type { Secrets } from '../types/secrets';
126
6
 
127
- function deleteCredentials(profile: string): void {
128
- const credentialsFile = getCredentialsFile(profile);
7
+ import { getScopes } from './scopes';
8
+ import { getSecretsFile } from './utils/paths';
129
9
 
130
- if (fs.existsSync(credentialsFile)) {
131
- fs.rmSync(credentialsFile);
132
- }
133
- }
10
+ const port = 6006;
11
+ const host = `localhost:${port}`;
12
+ const callbackURI = `http://${host}/oauthcallback`;
134
13
 
135
- function checkSecrets(profile: string, secretsObject: Secrets, secretsFile: string): true {
14
+ function checkSecrets(secretsObject: Secrets): true {
136
15
  if (secretsObject.web.redirect_uris[0] === callbackURI) {
137
16
  return true;
138
17
  }
139
- throw new Error(`Error in credentials file: redirect URI should be ${callbackURI}.\n${secrets.getSecretsError(profile, secretsFile)}`);
18
+ throw new Error(`Error in credentials file: redirect URI should be ${callbackURI}`);
140
19
  }
141
20
 
142
- function getScopesError(scopesFile: string): string {
143
- return [
144
- `File ${scopesFile} not found!`,
145
- `This application had to have pre-defined file ${scopesFile} that will declare needed scopes`,
146
- ].join('\n');
21
+ export function getSecrets(profile: string): Secrets {
22
+ const secretsFile = getSecretsFile(profile);
23
+ const secretsObject = fs.getJSON<Secrets>(secretsFile, () => {
24
+ throw new Error(getSecretsError(profile, secretsFile));
25
+ });
26
+ checkSecrets(secretsObject);
27
+ return secretsObject;
147
28
  }
148
29
 
149
30
  function getSecretsError(profile: string, secretsFile: string): string {
@@ -166,7 +47,7 @@ function getSecretsError(profile: string, secretsFile: string): string {
166
47
  '\t\t\t\tSpecify your email as user support email and as developer contact information on the very bottom',
167
48
  '\t\t\t\tClick "Save and continue"',
168
49
  '\t\t\tClick "Add or remove scopes"',
169
- `\t\t\t\tAdd scopes: ${secrets.getScopes().join(',')}`,
50
+ `\t\t\t\tAdd scopes: ${getScopes().join(',')}`,
170
51
  '\t\t\t\tClick "Save and continue"',
171
52
  '\t\t\tClick "Add users"',
172
53
  '\t\t\t\tAdd your email',
@@ -182,6 +63,3 @@ function getSecretsError(profile: string, secretsFile: string): string {
182
63
  'Then start this script again',
183
64
  ].join('\n');
184
65
  }
185
-
186
- export { getSecrets, getCredentials, deleteCredentials };
187
- export default { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, deleteCredentials, checkSecrets, getSecretsError, getScopesError };
@@ -0,0 +1,30 @@
1
+ import path from 'path';
2
+
3
+ import type { templates } from '../renderer';
4
+
5
+ const dirPaths = {
6
+ input : 'input',
7
+ secrets : 'secrets',
8
+ // TODO: Remove this hack after moving to React
9
+ templates: path.relative(process.cwd(), path.join(__dirname, '../../templates')),
10
+ };
11
+
12
+ export function getProfilesFile(): string {
13
+ return path.join(dirPaths.input, 'profiles.json');
14
+ }
15
+
16
+ export function getScopesFile(): string {
17
+ return 'scopes.json';
18
+ }
19
+
20
+ export function getSecretsFile(profile: string): string {
21
+ return path.join(dirPaths.secrets, `${profile}.json`);
22
+ }
23
+
24
+ export function getCredentialsFile(profile: string): string {
25
+ return path.join(dirPaths.secrets, `${profile}.credentials.json`);
26
+ }
27
+
28
+ export function getTemplateFile(templateName: keyof typeof templates): string {
29
+ return path.join(dirPaths.templates, `${templateName}.html`);
30
+ }
@@ -1,7 +1,7 @@
1
1
  <h1>Welcome ${profile}!</h1>
2
2
  <p>Please authorize:</p>
3
3
  <ul>
4
- ${scopesList}
4
+ ${scopesList}
5
5
  </ul>
6
6
  <a id="button" href="${authUrl}">Continue</a>
7
7
  <script type="text/javascript">document.addEventListener('DOMContentLoaded', function(){ document.getElementById('button').focus(); });</script>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Google API sign-in'</title>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
7
+ </head>
8
+ <body>
9
+
10
+ ${style}
11
+
12
+ ${page}
13
+
14
+ ${script}
15
+
16
+ </body>
17
+ </html>
@@ -1,4 +1,3 @@
1
- ${css}
2
1
  <div class="box">
3
- ${content}
2
+ ${content}
4
3
  </div>
File without changes
@@ -11,8 +11,8 @@
11
11
  }
12
12
 
13
13
  body {
14
- font-family: Arial, sans-serif;
15
- font-size: 17px;
14
+ font-family: "Segoe UI", "Roboto", "Helvetica", sans-serif;
15
+ font-size: 16px;
16
16
  display: flex;
17
17
  align-items: center;
18
18
  justify-content: center;
@@ -1,10 +1,8 @@
1
- interface CommonOptions {
2
- hideProgress? : boolean;
1
+ export interface CommonOptions {
2
+ hideProgress?: boolean;
3
3
  }
4
4
 
5
- interface AuthOptions {
6
- temporary? : boolean;
7
- scopes? : string[];
5
+ export interface AuthOptions {
6
+ temporary?: boolean;
7
+ scopes?: string[];
8
8
  }
9
-
10
- export type { CommonOptions, AuthOptions };
@@ -1,13 +1,11 @@
1
- interface Secrets {
1
+ export interface Secrets {
2
2
  web: {
3
- client_id : `${string}.apps.googleusercontent.com`;
4
- project_id : string;
5
- auth_uri : 'https://accounts.google.com/o/oauth2/auth';
6
- token_uri : 'https://oauth2.googleapis.com/token';
7
- auth_provider_x509_cert_url : 'https://www.googleapis.com/oauth2/v1/certs';
8
- client_secret : string;
9
- redirect_uris : string[];
3
+ client_id: `${string}.apps.googleusercontent.com`;
4
+ project_id: string;
5
+ auth_uri: 'https://accounts.google.com/o/oauth2/auth';
6
+ token_uri: 'https://oauth2.googleapis.com/token';
7
+ auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs';
8
+ client_secret: string;
9
+ redirect_uris: string[];
10
10
  };
11
11
  }
12
-
13
- export type { Secrets };
@@ -3,5 +3,11 @@
3
3
 
4
4
  "exclude" : [
5
5
  "**/__tests__/",
6
+ "**/__mocks__/",
6
7
  ],
8
+
9
+ "compilerOptions" : {
10
+ "rootDir" : "./src",
11
+ "outDir" : "./dist",
12
+ },
7
13
  }
package/tsconfig.json CHANGED
@@ -1,11 +1,6 @@
1
1
  {
2
2
  "extends" : "./node_modules/@anmiles/tsconfig/tsconfig.json",
3
3
 
4
- "compilerOptions" : {
5
- "rootDir" : "./src",
6
- "outDir" : "./dist",
7
- },
8
-
9
4
  "include" : [
10
5
  "src",
11
6
  ],
@@ -2,6 +2,6 @@
2
2
  "extends" : "./tsconfig.json",
3
3
 
4
4
  "compilerOptions" : {
5
- "moduleResolution" : "Node",
5
+ "isolatedModules" : true, // otherwise tests are slowing down a lot because of googleapis
6
6
  },
7
7
  }
package/.eslintignore DELETED
@@ -1,2 +0,0 @@
1
- coverage
2
- dist