-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcli.js
executable file
·155 lines (136 loc) · 4.14 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env node
import process from 'node:process';
import {createServer} from 'node:http';
import * as p from '@clack/prompts';
import open from 'open';
import getPort from 'get-port';
import pDefer from 'p-defer';
if (typeof fetch !== 'function') {
throw new TypeError('This script requires Node.js 18.0 or newer because it relies on the global `fetch` function.');
}
const approvalCode = pDefer();
const port = await getPort();
const localhost = '127.0.0.1';
// TODO: Remove after https://github.com/natemoo-re/clack/issues/181
const tasks = async tasks => {
for (const task of tasks) {
if (task.enabled === false) {
continue;
}
const s = p.spinner();
s.start(task.title);
// eslint-disable-next-line no-await-in-loop -- Sequential
const result = await task.task(s.message);
s.stop(result || task.title);
}
};
const server = createServer((request, response) => {
const {searchParams} = new URL(request.url, serverUrl);
if (searchParams.has('code')) {
approvalCode.resolve(searchParams.get('code'));
// Html header
response.writeHead(200, {'Content-Type': 'text/html'});
response.end('You can close this tab now. <script>window.close()</script>');
server.close();
return;
}
response.writeHead(400, {'Content-Type': 'text/plain'});
response.end('No `code` found in the URL. WHO R U?');
});
// Start the server on port 3000
server.listen(port, localhost);
const serverUrl = `http://${localhost}:${port}`;
function required(input) {
if (input.trim() === '') {
return 'Required';
}
}
async function getRefreshToken() {
const request = await fetch('https://accounts.google.com/o/oauth2/token', {
method: 'POST',
body: new URLSearchParams([
['client_id', group.clientId.trim()],
['client_secret', group.clientSecret.trim()],
['code', code],
['grant_type', 'authorization_code'],
['redirect_uri', serverUrl], // Unused but required
]),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
if (!request.ok) {
throw new Error('Error while getting the refresh token: ' + request.statusText);
}
const response = await request.json();
if (response.error) {
throw new Error('Error while getting the refresh token: ' + response.error);
}
return response.refresh_token;
}
function getLoginUrl(clientId) {
const url = new URL('https://accounts.google.com/o/oauth2/auth');
url.searchParams.set('response_type', 'code');
url.searchParams.set('access_type', 'offline');
url.searchParams.set('client_id', clientId.trim());
url.searchParams.set('scope', 'https://www.googleapis.com/auth/chromewebstore');
url.searchParams.set('redirect_uri', serverUrl);
return url.href;
}
p.intro('Follow the steps at this URL to generate the API keys, then enter them below to generate the refresh token.\n https://github.com/fregante/chrome-webstore-upload-keys');
const group = await p.group(
{
clientId: () => p.text({
message: 'Client ID:',
placeholder: 'e.g. 960453266371-2qcq5fppm3d5e.apps.googleusercontent.com',
validate: required,
}),
clientSecret: () => p.text({
message: 'Client secret:',
placeholder: 'e.g. GOCSPX-O9uS1FLnCqXDvru7Y_',
validate: required,
}),
open: () => p.confirm({
message: 'Open the authentication page in the default browser?',
}),
},
{
onCancel() {
p.cancel('Operation cancelled.');
process.exit(0); // `onCancel` continues to the next question
},
},
);
let code;
let refreshToken;
await tasks([
{
title: 'Opening the login page in the browser',
async task() {
const instructions = 'Complete the process in the browser. Follow its steps and warnings (this is your own personal app)';
if (group.open) {
await open(getLoginUrl(group.clientId));
return instructions;
}
return instructions + '\n\n ' + getLoginUrl(group.clientId);
},
},
{
title: 'Waiting for you in the browser',
async task() {
code = await approvalCode.promise;
return 'Approval code received from Google';
},
},
{
title: 'Asking Google for the refresh token',
async task() {
refreshToken = await getRefreshToken();
return `Done:
CLIENT_ID=${group.clientId}
CLIENT_SECRET=${group.clientSecret}
REFRESH_TOKEN=${refreshToken}
`;
},
},
]);