@digital-ai/dot-illustrations 2.0.43 → 2.0.46
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/.github/workflows/publish.yml +1 -1
- package/README.md +8 -4
- package/demo/device-flow-poll.test.js +110 -0
- package/demo/github-upload.js +120 -13
- package/demo/oauth-proxy/README.md +23 -0
- package/demo/oauth-proxy/worker.js +62 -0
- package/demo/oauth-proxy/wrangler.toml +6 -0
- package/demo/script.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -106,7 +106,11 @@ The demo includes a built-in upload flow that handles everything automatically.
|
|
|
106
106
|
|
|
107
107
|
#### Prerequisites
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
**On the hosted demo** (`digital-ai.github.io/dot-illustrations/demo/`): nothing to run yourself — click **Connect GitHub** and follow the device-code prompt. This works because a maintainer has deployed the `demo/oauth-proxy/` Worker + registered a GitHub OAuth App (one-time setup, see [`demo/oauth-proxy/`](demo/oauth-proxy/)). GitHub's device-flow endpoints don't support CORS, so the browser can't call them directly — the Worker forwards those two calls server-to-server. It holds no secrets; Device Flow for public OAuth Apps only needs the Client ID.
|
|
110
|
+
|
|
111
|
+
If that isn't configured yet, **Connect GitHub** falls back to a manual Personal Access Token paste-in (scope: `public_repo`).
|
|
112
|
+
|
|
113
|
+
**Running locally instead:** run the local proxy once so the demo can authenticate as you without entering a token:
|
|
110
114
|
|
|
111
115
|
```bash
|
|
112
116
|
# From the repo root (requires gh CLI authenticated: gh auth login)
|
|
@@ -123,19 +127,19 @@ node demo/local-proxy.js
|
|
|
123
127
|
- Enter the **Illustration ID** (lowercase, hyphens only — e.g. `my-new-state`)
|
|
124
128
|
- Select the **Category**: Global or Dashboards
|
|
125
129
|
- Upload the **Light SVG** (☀️) and **Dark SVG** (🌙) — both are required
|
|
126
|
-
- Click **Connect GitHub** then **Create Pull Request**
|
|
130
|
+
- Click **Connect GitHub** (device-code prompt on the hosted demo, local `gh auth` session with the local proxy running, or PAT paste-in fallback) then **Create Pull Request**
|
|
127
131
|
|
|
128
132
|
**Integration logo**
|
|
129
133
|
- Enter the **Integration ID** (lowercase, hyphens — e.g. `my-tool`)
|
|
130
134
|
- Upload the single **SVG** file (no dark variant needed)
|
|
131
|
-
- Click **Connect GitHub** then **Create Pull Request**
|
|
135
|
+
- Click **Connect GitHub** (device-code prompt on the hosted demo, local `gh auth` session with the local proxy running, or PAT paste-in fallback) then **Create Pull Request**
|
|
132
136
|
|
|
133
137
|
The PR is created automatically with:
|
|
134
138
|
- SVG file(s) committed to the correct folder
|
|
135
139
|
- CSS rule added to `index.css`
|
|
136
140
|
- ID added to `demo/script.js` in alphabetical order
|
|
137
141
|
|
|
138
|
-
> The local proxy uses your existing `gh auth` credentials — no token input required.
|
|
142
|
+
> The local proxy uses your existing `gh auth` credentials — no token input required. The hosted demo's Connect GitHub button uses the device-flow proxy instead (see Prerequisites above); if it's not yet configured, it falls back to PAT paste-in.
|
|
139
143
|
|
|
140
144
|
---
|
|
141
145
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Regression test for the device-flow login polling in github-upload.js.
|
|
4
|
+
*
|
|
5
|
+
* Guards the fix from #90: ghPollForToken must honor GitHub's `slow_down`
|
|
6
|
+
* backoff and must not fire a burst of sub-interval polls when the tab is
|
|
7
|
+
* refocused after the user authorises — otherwise the connect modal stalls
|
|
8
|
+
* forever on the code screen. Ported from digital-ai/dot-icons, which hit
|
|
9
|
+
* and fixed the same bug in the same demo pattern (icons #85, #86).
|
|
10
|
+
*
|
|
11
|
+
* It runs the ACTUAL ghPollForToken extracted from github-upload.js (not a
|
|
12
|
+
* copy) against a mocked fetch/document/window, replaying the real failure
|
|
13
|
+
* scenario: a focus + visibilitychange refocus burst, a `slow_down` from
|
|
14
|
+
* GitHub, then the token. Zero dependencies — plain Node.
|
|
15
|
+
*
|
|
16
|
+
* Run: node demo/device-flow-poll.test.js (or: npm test)
|
|
17
|
+
*/
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// ── Extract ghPollForToken from the source by brace-matching ────────────────
|
|
24
|
+
const src = fs.readFileSync(path.join(__dirname, 'github-upload.js'), 'utf8');
|
|
25
|
+
const anchor = src.indexOf('function ghPollForToken');
|
|
26
|
+
if (anchor === -1) fail('ghPollForToken not found in github-upload.js');
|
|
27
|
+
let depth = 0, end = -1, started = false;
|
|
28
|
+
for (let i = anchor; i < src.length; i++) {
|
|
29
|
+
const c = src[i];
|
|
30
|
+
if (c === '{') { depth++; started = true; }
|
|
31
|
+
else if (c === '}') { depth--; if (started && depth === 0) { end = i + 1; break; } }
|
|
32
|
+
}
|
|
33
|
+
if (end === -1) fail('could not brace-match ghPollForToken body');
|
|
34
|
+
const fnText = src.slice(anchor, end);
|
|
35
|
+
|
|
36
|
+
// ── Browser stubs ───────────────────────────────────────────────────────────
|
|
37
|
+
const listeners = { visibilitychange: [], focus: [] };
|
|
38
|
+
const document = {
|
|
39
|
+
visibilityState: 'visible',
|
|
40
|
+
addEventListener: (e, cb) => listeners[e] && listeners[e].push(cb),
|
|
41
|
+
removeEventListener: (e, cb) => { if (listeners[e]) listeners[e] = listeners[e].filter(f => f !== cb); },
|
|
42
|
+
};
|
|
43
|
+
const window = {
|
|
44
|
+
addEventListener: (e, cb) => listeners[e] && listeners[e].push(cb),
|
|
45
|
+
removeEventListener: (e, cb) => { if (listeners[e]) listeners[e] = listeners[e].filter(f => f !== cb); },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Scripted GitHub responses, one per /access_token poll.
|
|
49
|
+
const RESPONSES = [
|
|
50
|
+
{ error: 'authorization_pending' },
|
|
51
|
+
{ error: 'slow_down', interval: 10 }, // GitHub raises the required interval to 10s
|
|
52
|
+
{ access_token: 'gho_REGRESSION_TEST_TOKEN' },
|
|
53
|
+
];
|
|
54
|
+
const pollTimes = [];
|
|
55
|
+
let step = 0;
|
|
56
|
+
const t0 = Date.now();
|
|
57
|
+
global.fetch = async () => {
|
|
58
|
+
pollTimes.push(Date.now() - t0);
|
|
59
|
+
const body = RESPONSES[Math.min(step, RESPONSES.length - 1)];
|
|
60
|
+
step++;
|
|
61
|
+
return { ok: true, status: 200, json: async () => body };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const GITHUB_UPLOAD_CONFIG = { OAUTH_PROXY_URL: 'https://proxy.test', CLIENT_ID: 'test' };
|
|
65
|
+
|
|
66
|
+
let ghPollForToken;
|
|
67
|
+
eval(`ghPollForToken = ${fnText}`);
|
|
68
|
+
|
|
69
|
+
// ── Run the scenario ────────────────────────────────────────────────────────
|
|
70
|
+
function fail(msg) { console.error(`\nFAIL ❌ — ${msg}`); process.exit(1); }
|
|
71
|
+
|
|
72
|
+
(async () => {
|
|
73
|
+
const promise = ghPollForToken('device-code', 5, 899);
|
|
74
|
+
|
|
75
|
+
// Refocus burst shortly after start: both events fire in the same tick.
|
|
76
|
+
// The fixed code must throttle these to the current interval.
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
listeners.visibilitychange.forEach(cb => cb());
|
|
79
|
+
listeners.focus.forEach(cb => cb());
|
|
80
|
+
}, 1200);
|
|
81
|
+
|
|
82
|
+
let token;
|
|
83
|
+
try {
|
|
84
|
+
token = await promise;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
fail(`polling rejected unexpectedly: ${e.message}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const gaps = pollTimes.slice(1).map((t, i) => t - pollTimes[i]);
|
|
90
|
+
|
|
91
|
+
if (token !== 'gho_REGRESSION_TEST_TOKEN')
|
|
92
|
+
fail(`expected the token to resolve, got: ${token}`);
|
|
93
|
+
if (pollTimes.length < 3)
|
|
94
|
+
fail(`expected at least 3 polls (pending, slow_down, token), got ${pollTimes.length}`);
|
|
95
|
+
// No self-inflicted burst: no two polls closer than ~the 5s interval, even
|
|
96
|
+
// though a focus+visibilitychange pair fired mid-flight.
|
|
97
|
+
const MIN_GAP = 4800;
|
|
98
|
+
const burst = gaps.find(g => g < MIN_GAP);
|
|
99
|
+
if (burst !== undefined)
|
|
100
|
+
fail(`polls fired ${Math.round(burst)}ms apart (< ${MIN_GAP}ms) — refocus burst not throttled`);
|
|
101
|
+
// Backoff: the interval must widen after slow_down (10s gap > the initial 5s).
|
|
102
|
+
if (!(gaps[1] > gaps[0] + 3000))
|
|
103
|
+
fail(`interval did not widen after slow_down (gaps ${gaps.map(Math.round)})`);
|
|
104
|
+
|
|
105
|
+
console.log('device-flow polling regression test');
|
|
106
|
+
console.log(` poll times (ms): ${pollTimes.map(Math.round).join(', ')}`);
|
|
107
|
+
console.log(` gaps (ms): ${gaps.map(Math.round).join(', ')}`);
|
|
108
|
+
console.log('\nPASS ✅ — token resolved, no sub-interval burst, slow_down widened the interval');
|
|
109
|
+
process.exit(0);
|
|
110
|
+
})();
|
package/demo/github-upload.js
CHANGED
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* 1. Register a GitHub OAuth App at https://github.com/settings/developers
|
|
7
7
|
* - Homepage: https://digital-ai.github.io/dot-illustrations/demo/
|
|
8
8
|
* - Check "Enable Device Flow" — no callback URL needed
|
|
9
|
-
* 2.
|
|
9
|
+
* 2. Replace CLIENT_ID below with your app's Client ID
|
|
10
|
+
* 3. The Client ID is public-safe — only the Client Secret must stay private,
|
|
11
|
+
* and Device Flow never requires the secret in the browser.
|
|
12
|
+
* 4. Deploy demo/oauth-proxy/ (Cloudflare Worker) — GitHub's device-flow
|
|
13
|
+
* endpoints don't support CORS, so the browser can't call them directly;
|
|
14
|
+
* the Worker forwards those two calls server-to-server. Holds no secrets.
|
|
15
|
+
* 5. Replace OAUTH_PROXY_URL below with the deployed Worker's *.workers.dev URL.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
const GITHUB_UPLOAD_CONFIG = {
|
|
13
|
-
CLIENT_ID: '
|
|
19
|
+
CLIENT_ID: 'Ov23liJSaMsvgE5Q51cI',
|
|
20
|
+
OAUTH_PROXY_URL: 'https://dot-illustrations-oauth-proxy.dot-icons.workers.dev',
|
|
14
21
|
REPO_OWNER: 'digital-ai',
|
|
15
22
|
REPO_NAME: 'dot-illustrations',
|
|
16
23
|
BASE_BRANCH: 'main',
|
|
@@ -132,12 +139,20 @@ async function ghCreatePR(head, title, body) {
|
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
// ─── Device Flow ─────────────────────────────────────────────────────────────
|
|
142
|
+
// Both calls go through demo/oauth-proxy/ (Cloudflare Worker) — GitHub's
|
|
143
|
+
// device-flow endpoints don't support CORS, so the browser can't call
|
|
144
|
+
// github.com directly.
|
|
135
145
|
|
|
136
146
|
async function ghStartDeviceFlow() {
|
|
137
|
-
if (
|
|
147
|
+
if (
|
|
148
|
+
!GITHUB_UPLOAD_CONFIG.CLIENT_ID ||
|
|
149
|
+
GITHUB_UPLOAD_CONFIG.CLIENT_ID === 'YOUR_GITHUB_OAUTH_CLIENT_ID' ||
|
|
150
|
+
!GITHUB_UPLOAD_CONFIG.OAUTH_PROXY_URL ||
|
|
151
|
+
GITHUB_UPLOAD_CONFIG.OAUTH_PROXY_URL === 'YOUR_OAUTH_PROXY_WORKERS_DEV_URL'
|
|
152
|
+
) {
|
|
138
153
|
throw new Error('NO_CLIENT_ID');
|
|
139
154
|
}
|
|
140
|
-
const resp = await fetch(
|
|
155
|
+
const resp = await fetch(`${GITHUB_UPLOAD_CONFIG.OAUTH_PROXY_URL}/device/code`, {
|
|
141
156
|
method: 'POST',
|
|
142
157
|
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
143
158
|
body: JSON.stringify({ client_id: GITHUB_UPLOAD_CONFIG.CLIENT_ID, scope: GITHUB_UPLOAD_CONFIG.SCOPES }),
|
|
@@ -146,11 +161,64 @@ async function ghStartDeviceFlow() {
|
|
|
146
161
|
return resp.json();
|
|
147
162
|
}
|
|
148
163
|
|
|
149
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Poll GitHub (via the oauth-proxy Worker) until the user authorises (or times out).
|
|
166
|
+
* Resolves with the access_token string.
|
|
167
|
+
*
|
|
168
|
+
* The intended UX is to approve the code on another tab/device while this tab
|
|
169
|
+
* waits — which backgrounds this tab, and browsers throttle setInterval in
|
|
170
|
+
* background tabs (down to ~once/minute or slower). The token is usually ready
|
|
171
|
+
* within seconds of approval, so also poll immediately on `visibilitychange`
|
|
172
|
+
* and `focus` to catch up as soon as the user switches back, instead of
|
|
173
|
+
* waiting on the throttled timer.
|
|
174
|
+
*
|
|
175
|
+
* A single failed poll (transient network blip, a fetch aborted because the
|
|
176
|
+
* tab was backgrounded, etc.) must not kill the whole flow — it just retries
|
|
177
|
+
* on the next tick. GitHub's own `expired_token` response is the normal stop
|
|
178
|
+
* condition, but if the proxy itself is unreachable we'd never get that
|
|
179
|
+
* response from GitHub at all — so `expiresInSec` also bounds retries
|
|
180
|
+
* client-side to the code's real lifetime, instead of polling forever.
|
|
181
|
+
*
|
|
182
|
+
* GitHub's device-flow spec mandates a client-side backoff: when the token
|
|
183
|
+
* endpoint answers `slow_down`, the client MUST raise its polling interval
|
|
184
|
+
* (GitHub returns the new minimum in the response, otherwise add 5s) and use
|
|
185
|
+
* the larger value from then on. Polling faster than the current interval
|
|
186
|
+
* just gets `slow_down` again — so a client that ignores it never receives
|
|
187
|
+
* the token and the flow stalls on the code screen even after the user has
|
|
188
|
+
* authorised. The `focus`/`visibilitychange` wake-ups (which catch up the
|
|
189
|
+
* throttled background timer as soon as the user switches back) are exactly
|
|
190
|
+
* what would otherwise fire a burst of sub-interval polls and trip
|
|
191
|
+
* `slow_down` in the first place, so they are throttled to the current
|
|
192
|
+
* interval too, and only ever one poll is in flight at a time.
|
|
193
|
+
*/
|
|
194
|
+
function ghPollForToken(deviceCode, intervalSec, expiresInSec) {
|
|
150
195
|
return new Promise((resolve, reject) => {
|
|
151
|
-
|
|
196
|
+
let settled = false;
|
|
197
|
+
let inFlight = false;
|
|
198
|
+
let intervalMs = Math.max(intervalSec || 5, 5) * 1000;
|
|
199
|
+
let lastPollAt = 0;
|
|
200
|
+
let timer = null;
|
|
201
|
+
const deadline = Date.now() + (expiresInSec || 900) * 1000;
|
|
202
|
+
|
|
203
|
+
// (Re)arm the next poll `delay` ms from now (clamped to ≥0, so an
|
|
204
|
+
// already-due wake-up polls immediately). Always supersedes any pending
|
|
205
|
+
// timer so the interval can grow after a `slow_down`.
|
|
206
|
+
function schedule(delay) {
|
|
207
|
+
if (settled) return;
|
|
208
|
+
clearTimeout(timer);
|
|
209
|
+
timer = setTimeout(poll, Math.max(delay, 0));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function poll() {
|
|
213
|
+
if (settled || inFlight) return;
|
|
214
|
+
if (Date.now() > deadline) {
|
|
215
|
+
settle(() => reject(new Error('Device code expired — please try again.')));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
inFlight = true;
|
|
219
|
+
lastPollAt = Date.now();
|
|
152
220
|
try {
|
|
153
|
-
const resp = await fetch(
|
|
221
|
+
const resp = await fetch(`${GITHUB_UPLOAD_CONFIG.OAUTH_PROXY_URL}/access_token`, {
|
|
154
222
|
method: 'POST',
|
|
155
223
|
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
156
224
|
body: JSON.stringify({
|
|
@@ -159,13 +227,52 @@ function ghPollForToken(deviceCode, intervalSec) {
|
|
|
159
227
|
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
160
228
|
}),
|
|
161
229
|
});
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
230
|
+
const data = await resp.json();
|
|
231
|
+
if (settled) return;
|
|
232
|
+
if (data.access_token) {
|
|
233
|
+
settle(() => resolve(data.access_token));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (data.error === 'expired_token' || data.error === 'access_denied') {
|
|
237
|
+
settle(() => reject(new Error(data.error_description || data.error)));
|
|
238
|
+
return;
|
|
166
239
|
}
|
|
167
|
-
|
|
168
|
-
|
|
240
|
+
if (data.error === 'slow_down') {
|
|
241
|
+
// Honor GitHub's backoff: use the interval it hands back, else +5s.
|
|
242
|
+
intervalMs = Math.max(data.interval ? data.interval * 1000 : intervalMs + 5000,
|
|
243
|
+
intervalMs + 5000);
|
|
244
|
+
}
|
|
245
|
+
// 'authorization_pending' or 'slow_down' — keep polling on the interval
|
|
246
|
+
} catch (err) {
|
|
247
|
+
// Transient failure (network blip, backgrounded-tab fetch abort) —
|
|
248
|
+
// keep polling rather than killing the whole flow.
|
|
249
|
+
console.warn('Device-flow poll failed, retrying:', err);
|
|
250
|
+
} finally {
|
|
251
|
+
inFlight = false;
|
|
252
|
+
}
|
|
253
|
+
schedule(intervalMs);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function settle(fn) {
|
|
257
|
+
settled = true;
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
document.removeEventListener('visibilitychange', onVisible);
|
|
260
|
+
window.removeEventListener('focus', onVisible);
|
|
261
|
+
fn();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Wake up as soon as the user switches back to this tab, but never poll
|
|
265
|
+
// sooner than the current interval since the last poll — otherwise the
|
|
266
|
+
// focus + visibilitychange pair fires two sub-interval polls and trips
|
|
267
|
+
// GitHub's `slow_down`. If a poll is already due, this fires ~immediately.
|
|
268
|
+
function onVisible() {
|
|
269
|
+
if (settled || document.visibilityState !== 'visible') return;
|
|
270
|
+
schedule(intervalMs - (Date.now() - lastPollAt));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
document.addEventListener('visibilitychange', onVisible);
|
|
274
|
+
window.addEventListener('focus', onVisible);
|
|
275
|
+
poll(); // first poll now; it self-schedules the rest
|
|
169
276
|
});
|
|
170
277
|
}
|
|
171
278
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# OAuth device-flow proxy
|
|
2
|
+
|
|
3
|
+
Stateless Cloudflare Worker that forwards `POST /device/code` and `POST /access_token`
|
|
4
|
+
to GitHub's device-flow endpoints. Exists only because those endpoints don't support
|
|
5
|
+
CORS, so a static page can't call them directly from the browser. Holds no secrets —
|
|
6
|
+
device flow for public GitHub OAuth Apps only needs the Client ID, which the browser
|
|
7
|
+
already sends.
|
|
8
|
+
|
|
9
|
+
## Deploy (one-time, per maintainer/environment)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd demo/oauth-proxy
|
|
13
|
+
npx wrangler login
|
|
14
|
+
npx wrangler deploy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This prints a `*.workers.dev` URL. Then:
|
|
18
|
+
|
|
19
|
+
1. Put that URL into `OAUTH_PROXY_URL` in [`demo/github-upload.js`](../github-upload.js)
|
|
20
|
+
2. Put your GitHub OAuth App's Client ID into `CLIENT_ID` in the same file (see the
|
|
21
|
+
file's header comment for how to register the OAuth App)
|
|
22
|
+
3. If the demo isn't served from `https://digital-ai.github.io`, update
|
|
23
|
+
`ALLOWED_ORIGIN` in [`wrangler.toml`](./wrangler.toml) to match
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth device-flow proxy — Cloudflare Worker
|
|
3
|
+
*
|
|
4
|
+
* GitHub's device-flow endpoints (github.com/login/device/code and
|
|
5
|
+
* github.com/login/oauth/access_token) don't support CORS, so a static
|
|
6
|
+
* page can't call them directly from browser JS. This Worker forwards
|
|
7
|
+
* those two calls server-to-server, so the browser only ever talks to
|
|
8
|
+
* this Worker (which does send CORS headers).
|
|
9
|
+
*
|
|
10
|
+
* Holds no secrets: GitHub's device flow for public OAuth Apps only
|
|
11
|
+
* needs client_id (sent by the browser), never a client secret.
|
|
12
|
+
*
|
|
13
|
+
* Deploy: from this directory, `npx wrangler login && npx wrangler deploy`.
|
|
14
|
+
* Then set ALLOWED_ORIGIN below (or via `wrangler.toml` [vars]) and put the
|
|
15
|
+
* resulting *.workers.dev URL into demo/github-upload.js's OAUTH_PROXY_URL.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const GITHUB_ENDPOINTS = {
|
|
19
|
+
'/device/code': 'https://github.com/login/device/code',
|
|
20
|
+
'/access_token': 'https://github.com/login/oauth/access_token',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
async fetch(request, env) {
|
|
25
|
+
const allowedOrigin = env.ALLOWED_ORIGIN || 'https://digital-ai.github.io';
|
|
26
|
+
const corsHeaders = {
|
|
27
|
+
'Access-Control-Allow-Origin': allowedOrigin,
|
|
28
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
29
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (request.method === 'OPTIONS') {
|
|
33
|
+
return new Response(null, { headers: corsHeaders });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const url = new URL(request.url);
|
|
37
|
+
const target = GITHUB_ENDPOINTS[url.pathname];
|
|
38
|
+
|
|
39
|
+
if (!target || request.method !== 'POST') {
|
|
40
|
+
return new Response(JSON.stringify({ error: 'Not found' }), {
|
|
41
|
+
status: 404,
|
|
42
|
+
headers: { 'Content-Type': 'application/json', ...corsHeaders },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const body = await request.text();
|
|
47
|
+
const upstream = await fetch(target, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Accept': 'application/json',
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
body,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const data = await upstream.text();
|
|
57
|
+
return new Response(data, {
|
|
58
|
+
status: upstream.status,
|
|
59
|
+
headers: { 'Content-Type': 'application/json', ...corsHeaders },
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
package/demo/script.js
CHANGED
|
@@ -632,7 +632,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
632
632
|
document.getElementById('gh-device-code').textContent = user_code;
|
|
633
633
|
document.getElementById('gh-copy-code-btn').onclick = () =>
|
|
634
634
|
navigator.clipboard.writeText(user_code).then(() => showToast('Code copied!'));
|
|
635
|
-
const token = await window.GitHubUpload.pollForToken(device_code, interval || 5);
|
|
635
|
+
const token = await window.GitHubUpload.pollForToken(device_code, interval || 5, expires_in);
|
|
636
636
|
window.GitHubUpload.setToken(token);
|
|
637
637
|
const user = await window.GitHubUpload.getUser();
|
|
638
638
|
updateAuthBadge(user);
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digital-ai/dot-illustrations",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.46",
|
|
4
4
|
"description": "A central place for the design team to keep illustrations and for dev teams to find them.",
|
|
5
5
|
"main": "./index.css",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "node demo/device-flow-poll.test.js"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|