@atlasauth/js 0.1.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 (46) hide show
  1. package/dist/attempt.d.ts +71 -0
  2. package/dist/attempt.js +89 -0
  3. package/dist/attempt.js.map +1 -0
  4. package/dist/check-session.d.ts +56 -0
  5. package/dist/check-session.js +106 -0
  6. package/dist/check-session.js.map +1 -0
  7. package/dist/connect.d.ts +50 -0
  8. package/dist/connect.js +72 -0
  9. package/dist/connect.js.map +1 -0
  10. package/dist/fapi.d.ts +123 -0
  11. package/dist/fapi.js +314 -0
  12. package/dist/fapi.js.map +1 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +43 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/native.d.ts +112 -0
  17. package/dist/native.js +201 -0
  18. package/dist/native.js.map +1 -0
  19. package/dist/passkey.d.ts +70 -0
  20. package/dist/passkey.js +126 -0
  21. package/dist/passkey.js.map +1 -0
  22. package/dist/password-reset.d.ts +41 -0
  23. package/dist/password-reset.js +80 -0
  24. package/dist/password-reset.js.map +1 -0
  25. package/dist/reauth.d.ts +29 -0
  26. package/dist/reauth.js +45 -0
  27. package/dist/reauth.js.map +1 -0
  28. package/dist/redirect.d.ts +38 -0
  29. package/dist/redirect.js +56 -0
  30. package/dist/redirect.js.map +1 -0
  31. package/dist/siwe.d.ts +23 -0
  32. package/dist/siwe.js +28 -0
  33. package/dist/siwe.js.map +1 -0
  34. package/dist/tab-election.d.ts +68 -0
  35. package/dist/tab-election.js +111 -0
  36. package/dist/tab-election.js.map +1 -0
  37. package/dist/telegram.d.ts +25 -0
  38. package/dist/telegram.js +18 -0
  39. package/dist/telegram.js.map +1 -0
  40. package/dist/telemetry.d.ts +125 -0
  41. package/dist/telemetry.js +357 -0
  42. package/dist/telemetry.js.map +1 -0
  43. package/dist/token-cache.d.ts +74 -0
  44. package/dist/token-cache.js +104 -0
  45. package/dist/token-cache.js.map +1 -0
  46. package/package.json +26 -0
@@ -0,0 +1,357 @@
1
+ "use strict";
2
+ /**
3
+ * §bot — client-side signal collection for the anti-bot pipeline.
4
+ *
5
+ * Two collectors, both privacy-preserving and both fully best-effort (they never
6
+ * throw, and degrade to nulls in any environment):
7
+ *
8
+ * - `collectDeviceSignals()` — a snapshot of the device/environment: screen,
9
+ * timezone, hardware, a coarse canvas/WebGL fingerprint, and AUTOMATION
10
+ * TELLS (navigator.webdriver, headless markers, Selenium/Puppeteer traces).
11
+ * - `BehaviorRecorder` — accumulates interaction TIMING AGGREGATES on the auth
12
+ * form: mouse kinematics, keystroke dwell/flight, time-to-submit, scrolls.
13
+ * It records COUNTS AND TIMINGS ONLY — never which keys were pressed, never
14
+ * field values. There is nothing sensitive to leak.
15
+ *
16
+ * `sendTelemetry()` posts a snapshot to `/v1/client/telemetry`. It is
17
+ * fire-and-forget: a failure resolves `false` and is swallowed, so telemetry can
18
+ * never interfere with a real sign-in.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.BehaviorRecorder = void 0;
22
+ exports.collectDeviceSignals = collectDeviceSignals;
23
+ exports.deviceId = deviceId;
24
+ exports.deviceFingerprint = deviceFingerprint;
25
+ exports.sendTelemetry = sendTelemetry;
26
+ const hasWindow = typeof window !== 'undefined';
27
+ const nav = () => (typeof navigator !== 'undefined' ? navigator : undefined);
28
+ function safe(fn, fallback) {
29
+ try {
30
+ return fn();
31
+ }
32
+ catch {
33
+ return fallback;
34
+ }
35
+ }
36
+ /** WebGL unmasked vendor/renderer, if exposed. */
37
+ function webglInfo() {
38
+ return safe(() => {
39
+ if (!hasWindow || typeof document === 'undefined')
40
+ return { vendor: null, renderer: null };
41
+ const canvas = document.createElement('canvas');
42
+ const gl = (canvas.getContext('webgl') ||
43
+ canvas.getContext('experimental-webgl'));
44
+ if (!gl)
45
+ return { vendor: null, renderer: null };
46
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
47
+ const vendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : null;
48
+ const renderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : null;
49
+ return { vendor: vendor ?? null, renderer: renderer ?? null };
50
+ }, { vendor: null, renderer: null });
51
+ }
52
+ /** A short non-crypto canvas fingerprint (a bucket key, never a credential). */
53
+ function canvasHash() {
54
+ return safe(() => {
55
+ if (typeof document === 'undefined')
56
+ return null;
57
+ const canvas = document.createElement('canvas');
58
+ canvas.width = 220;
59
+ canvas.height = 40;
60
+ const ctx = canvas.getContext('2d');
61
+ if (!ctx)
62
+ return null;
63
+ ctx.textBaseline = 'top';
64
+ ctx.font = "14px 'Arial'";
65
+ ctx.fillStyle = '#f60';
66
+ ctx.fillRect(0, 0, 60, 20);
67
+ ctx.fillStyle = '#069';
68
+ ctx.fillText('Atlas anti-bot ✈ 1.0', 2, 15);
69
+ return djb2(canvas.toDataURL());
70
+ }, null);
71
+ }
72
+ /** Detect common headless/automation markers without being fooled by UA alone. */
73
+ function automationTells() {
74
+ const n = nav();
75
+ const w = hasWindow ? window : {};
76
+ const ua = n?.userAgent ?? '';
77
+ const webdriver = safe(() => n?.webdriver === true, false);
78
+ const hasChrome = safe(() => 'chrome' in w && !!w.chrome, false);
79
+ const headless = /headless/i.test(ua) ||
80
+ // A Chrome UA with no chrome object and no plugins is a classic headless tell.
81
+ (/chrome/i.test(ua) && !hasChrome && safe(() => (n?.plugins?.length ?? 0) === 0, false));
82
+ const automationHint = safe(() => webdriver ||
83
+ '_phantom' in w ||
84
+ 'callPhantom' in w ||
85
+ '__nightmare' in w ||
86
+ 'domAutomation' in w ||
87
+ 'domAutomationController' in w ||
88
+ // chromedriver injects cdc_* properties on document.
89
+ (typeof document !== 'undefined' &&
90
+ Object.keys(document).some((k) => k.startsWith('cdc_') || k.startsWith('$cdc_'))) ||
91
+ /electron|phantomjs|slimerjs/i.test(ua), webdriver);
92
+ return { webdriver, headless, hasChrome, automationHint };
93
+ }
94
+ function collectDeviceSignals() {
95
+ const n = nav();
96
+ const gl = webglInfo();
97
+ const tells = automationTells();
98
+ const languages = safe(() => Array.from(n?.languages ?? []), []);
99
+ const renderer = gl.renderer;
100
+ return {
101
+ screenWidth: safe(() => window.screen?.width ?? null, null),
102
+ screenHeight: safe(() => window.screen?.height ?? null, null),
103
+ colorDepth: safe(() => window.screen?.colorDepth ?? null, null),
104
+ pixelRatio: safe(() => window.devicePixelRatio ?? null, null),
105
+ timezone: safe(() => Intl.DateTimeFormat().resolvedOptions().timeZone ?? null, null),
106
+ timezoneOffset: safe(() => new Date().getTimezoneOffset(), null),
107
+ language: safe(() => n?.language ?? null, null),
108
+ languages,
109
+ platform: safe(() => n?.platform ?? null, null),
110
+ hardwareConcurrency: safe(() => n?.hardwareConcurrency ?? null, null),
111
+ deviceMemory: safe(() => n?.deviceMemory ?? null, null),
112
+ maxTouchPoints: safe(() => n?.maxTouchPoints ?? null, null),
113
+ touchSupport: safe(() => (n?.maxTouchPoints ?? 0) > 0 || 'ontouchstart' in window, false),
114
+ cookieEnabled: safe(() => n?.cookieEnabled ?? null, null),
115
+ doNotTrack: safe(() => n?.doNotTrack === '1' || n?.doNotTrack === 'yes', null),
116
+ webglVendor: gl.vendor,
117
+ webglRenderer: renderer,
118
+ pluginCount: safe(() => n?.plugins?.length ?? null, null),
119
+ pointerType: safe(() => (window.matchMedia?.('(pointer: fine)')?.matches ? 'fine' : 'coarse'), null),
120
+ webdriver: tells.webdriver,
121
+ headless: tells.headless,
122
+ hasChrome: tells.hasChrome,
123
+ webglSoftware: renderer ? /swiftshader|llvmpipe|software|basic render/i.test(renderer) : false,
124
+ automationHint: tells.automationHint,
125
+ // canvasHash folds into the device fingerprint below; kept off the wire shape
126
+ // to stay small, but influences deviceFingerprint().
127
+ };
128
+ }
129
+ /** A stable pseudonymous client id, persisted in localStorage. */
130
+ function deviceId() {
131
+ return safe(() => {
132
+ if (typeof localStorage === 'undefined')
133
+ return null;
134
+ const KEY = '__atlas_did';
135
+ let id = localStorage.getItem(KEY);
136
+ if (!id) {
137
+ id = 'd_' + djb2(String(Math.random()) + String(Date.now()) + (nav()?.userAgent ?? ''));
138
+ localStorage.setItem(KEY, id);
139
+ }
140
+ return id;
141
+ }, null);
142
+ }
143
+ /** A short client-side fingerprint over the stable device signals. */
144
+ function deviceFingerprint(device) {
145
+ const parts = [
146
+ device.timezone,
147
+ device.platform,
148
+ device.languages.join(','),
149
+ device.webglVendor,
150
+ device.webglRenderer,
151
+ device.screenWidth,
152
+ device.screenHeight,
153
+ device.colorDepth,
154
+ device.hardwareConcurrency,
155
+ canvasHash(),
156
+ ];
157
+ return djb2(parts.map((p) => (p == null ? '' : String(p))).join('|'));
158
+ }
159
+ /** A tiny deterministic string hash (djb2) → 8-hex. Not a credential. */
160
+ function djb2(s) {
161
+ let h = 5381;
162
+ for (let i = 0; i < s.length; i += 1)
163
+ h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
164
+ return h.toString(16).padStart(8, '0');
165
+ }
166
+ const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : null);
167
+ const variance = (xs) => {
168
+ const m = mean(xs);
169
+ if (m === null)
170
+ return null;
171
+ return xs.reduce((a, b) => a + (b - m) ** 2, 0) / xs.length;
172
+ };
173
+ /**
174
+ * Records interaction TIMINGS on the page/form. Start it when the auth form
175
+ * mounts; call `snapshot()` at submit. It listens passively and captures no
176
+ * content — only counts, distances and millisecond timings.
177
+ */
178
+ class BehaviorRecorder {
179
+ start = Date.now();
180
+ firstInteraction = null;
181
+ moves = 0;
182
+ distance = 0;
183
+ maxSpeed = 0;
184
+ speeds = [];
185
+ lastX = null;
186
+ lastY = null;
187
+ lastMoveT = 0;
188
+ netDx = 0;
189
+ netDy = 0;
190
+ keyCount = 0;
191
+ downAt = new Map();
192
+ dwell = [];
193
+ flight = [];
194
+ lastUpAt = null;
195
+ pastes = 0;
196
+ backspaces = 0;
197
+ focusChanges = 0;
198
+ scrolls = 0;
199
+ target;
200
+ bound = [];
201
+ constructor(target) {
202
+ this.target = target ?? (typeof document !== 'undefined' ? document : undefined);
203
+ this.attach();
204
+ }
205
+ mark() {
206
+ if (this.firstInteraction === null)
207
+ this.firstInteraction = Date.now() - this.start;
208
+ }
209
+ attach() {
210
+ const t = this.target;
211
+ if (!t)
212
+ return;
213
+ const on = (type, fn) => {
214
+ safe(() => t.addEventListener(type, fn, { passive: true }), undefined);
215
+ this.bound.push([type, fn]);
216
+ };
217
+ on('pointermove', (e) => this.onMove(e));
218
+ on('mousemove', (e) => this.onMove(e));
219
+ on('keydown', (e) => this.onKeyDown(e));
220
+ on('keyup', (e) => this.onKeyUp(e));
221
+ on('paste', () => {
222
+ this.mark();
223
+ this.pastes += 1;
224
+ });
225
+ on('focusin', () => {
226
+ this.mark();
227
+ this.focusChanges += 1;
228
+ });
229
+ on('scroll', () => {
230
+ this.mark();
231
+ this.scrolls += 1;
232
+ });
233
+ }
234
+ onMove(e) {
235
+ this.mark();
236
+ const x = e.clientX ?? 0;
237
+ const y = e.clientY ?? 0;
238
+ const now = Date.now();
239
+ if (this.lastX !== null && this.lastY !== null) {
240
+ const dx = x - this.lastX;
241
+ const dy = y - this.lastY;
242
+ const d = Math.hypot(dx, dy);
243
+ this.distance += d;
244
+ this.netDx += dx;
245
+ this.netDy += dy;
246
+ const dt = now - this.lastMoveT;
247
+ if (dt > 0) {
248
+ const speed = d / dt;
249
+ this.speeds.push(speed);
250
+ if (speed > this.maxSpeed)
251
+ this.maxSpeed = speed;
252
+ }
253
+ }
254
+ this.moves += 1;
255
+ this.lastX = x;
256
+ this.lastY = y;
257
+ this.lastMoveT = now;
258
+ }
259
+ onKeyDown(e) {
260
+ this.mark();
261
+ this.keyCount += 1;
262
+ if (e.key === 'Backspace')
263
+ this.backspaces += 1;
264
+ // Keyed by physical code only — never the character. Content is never read.
265
+ this.downAt.set(e.code || e.key || String(this.keyCount), Date.now());
266
+ if (this.lastUpAt !== null)
267
+ this.flight.push(Date.now() - this.lastUpAt);
268
+ }
269
+ onKeyUp(e) {
270
+ const k = e.code || e.key || '';
271
+ const down = this.downAt.get(k);
272
+ const now = Date.now();
273
+ if (down !== undefined) {
274
+ this.dwell.push(now - down);
275
+ this.downAt.delete(k);
276
+ }
277
+ this.lastUpAt = now;
278
+ }
279
+ /** A snapshot of aggregates so far. Safe to call repeatedly. */
280
+ snapshot() {
281
+ const netDist = Math.hypot(this.netDx, this.netDy);
282
+ return {
283
+ mouseMoves: this.moves,
284
+ mouseDistance: Math.round(this.distance),
285
+ mouseAvgSpeed: mean(this.speeds),
286
+ mouseMaxSpeed: Math.round(this.maxSpeed * 1000) / 1000,
287
+ mouseSpeedVariance: variance(this.speeds),
288
+ mouseStraightLineRatio: this.distance > 0 ? Math.min(1, netDist / this.distance) : null,
289
+ keyCount: this.keyCount,
290
+ keyAvgDwellMs: mean(this.dwell),
291
+ keyAvgFlightMs: mean(this.flight),
292
+ keyDwellVariance: variance(this.dwell),
293
+ keyFlightVariance: variance(this.flight),
294
+ pasteCount: this.pastes,
295
+ backspaceCount: this.backspaces,
296
+ timeToFirstInteractionMs: this.firstInteraction,
297
+ timeToSubmitMs: Date.now() - this.start,
298
+ focusChanges: this.focusChanges,
299
+ scrollCount: this.scrolls,
300
+ hadPointerMove: this.moves > 0,
301
+ hadKeydown: this.keyCount > 0,
302
+ interactionCount: this.moves + this.keyCount + this.scrolls + this.focusChanges,
303
+ };
304
+ }
305
+ /** Detach all listeners. */
306
+ stop() {
307
+ const t = this.target;
308
+ if (!t)
309
+ return;
310
+ for (const [type, fn] of this.bound)
311
+ safe(() => t.removeEventListener(type, fn), undefined);
312
+ this.bound = [];
313
+ }
314
+ }
315
+ exports.BehaviorRecorder = BehaviorRecorder;
316
+ /**
317
+ * Collect a device snapshot (+ behaviour from `recorder`, if given) and POST it.
318
+ * Resolves `true` when the beacon was accepted, `false` otherwise — never
319
+ * throws. Uses `navigator.sendBeacon` when possible so it survives navigation.
320
+ */
321
+ async function sendTelemetry(opts) {
322
+ return safe(async () => {
323
+ const device = collectDeviceSignals();
324
+ const payload = {
325
+ attempt_id: opts.attemptId,
326
+ kind: opts.kind ?? 'telemetry',
327
+ device_id: deviceId(),
328
+ device_fingerprint: deviceFingerprint(device),
329
+ device,
330
+ behavior: opts.recorder?.snapshot(),
331
+ };
332
+ const url = `${opts.api}/v1/client/telemetry?publishable_key=${encodeURIComponent(opts.publishableKey)}`;
333
+ const body = JSON.stringify(payload);
334
+ // Prefer sendBeacon in production: it is fire-and-forget and survives the
335
+ // page unloading on submit. It cannot set the publishable-key HEADER, hence
336
+ // the query param above. A caller that INJECTS a fetchImpl (tests, custom
337
+ // transports) means "use exactly this" — so sendBeacon is skipped then.
338
+ const n = nav();
339
+ if (!opts.fetchImpl && n && typeof n.sendBeacon === 'function' && typeof Blob !== 'undefined') {
340
+ const ok = n.sendBeacon(url, new Blob([body], { type: 'application/json' }));
341
+ if (ok)
342
+ return true;
343
+ }
344
+ const doFetch = opts.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);
345
+ if (!doFetch)
346
+ return false;
347
+ const res = await doFetch(url, {
348
+ method: 'POST',
349
+ headers: { 'content-type': 'application/json', 'x-publishable-key': opts.publishableKey },
350
+ body,
351
+ keepalive: true,
352
+ credentials: 'include',
353
+ });
354
+ return res.ok;
355
+ }, Promise.resolve(false));
356
+ }
357
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAwGH,oDAkCC;AAGD,4BAWC;AAGD,8CAcC;AA0MD,sCAkCC;AAnZD,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;AAChD,MAAM,GAAG,GAAG,GAA0B,EAAE,CAAC,CAAC,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AA+BpG,SAAS,IAAI,CAAI,EAAW,EAAE,QAAW;IACvC,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,kDAAkD;AAClD,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,GAAG,EAAE;QACf,IAAI,CAAC,SAAS,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC3F,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;YACpC,MAAM,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAiC,CAAC;QAC3E,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACjD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,qBAAqB,CAAY,CAAC,CAAC,CAAC,IAAI,CAAC;QACnF,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,uBAAuB,CAAY,CAAC,CAAC,CAAC,IAAI,CAAC;QACvF,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI,EAAE,CAAC;IAChE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,gFAAgF;AAChF,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,GAAG,EAAE;QACf,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO,IAAI,CAAC;QACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC;QACnB,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;QACzB,GAAG,CAAC,IAAI,GAAG,cAAc,CAAC;QAC1B,GAAG,CAAC,SAAS,GAAG,MAAM,CAAC;QACvB,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3B,GAAG,CAAC,SAAS,GAAG,MAAM,CAAC;QACvB,GAAG,CAAC,QAAQ,CAAC,sBAAsB,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAClC,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC;AAED,kFAAkF;AAClF,SAAS,eAAe;IACtB,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;IAChB,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAE,MAA6C,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjE,MAAM,QAAQ,GACZ,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,+EAA+E;QAC/E,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3F,MAAM,cAAc,GAAG,IAAI,CACzB,GAAG,EAAE,CACH,SAAS;QACT,UAAU,IAAI,CAAC;QACf,aAAa,IAAI,CAAC;QAClB,aAAa,IAAI,CAAC;QAClB,eAAe,IAAI,CAAC;QACpB,yBAAyB,IAAI,CAAC;QAC9B,qDAAqD;QACrD,CAAC,OAAO,QAAQ,KAAK,WAAW;YAC9B,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;QACnF,8BAA8B,CAAC,IAAI,CAAC,EAAE,CAAC,EACzC,SAAS,CACV,CAAC;IACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AAC5D,CAAC;AAED,SAAgB,oBAAoB;IAClC,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;IAChB,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC,EAAE,EAAc,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC7B,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI,EAAE,IAAI,CAAC;QAC3D,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI,EAAE,IAAI,CAAC;QAC7D,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI,EAAE,IAAI,CAAC;QAC/D,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,IAAI,IAAI,EAAE,IAAI,CAAC;QAC7D,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC;QACpF,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC;QAChE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC;QAC/C,SAAS;QACT,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC;QAC/C,mBAAmB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,mBAAmB,IAAI,IAAI,EAAE,IAAI,CAAC;QACrE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,CAAE,CAA0C,EAAE,YAAY,IAAI,IAAI,EAAE,IAAI,CAAC;QACjG,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,cAAc,IAAI,IAAI,EAAE,IAAI,CAAC;QAC3D,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,cAAc,IAAI,MAAM,EAAE,KAAK,CAAC;QACzF,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,IAAI,IAAI,EAAE,IAAI,CAAC;QACzD,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,GAAG,IAAK,CAAwC,EAAE,UAAU,KAAK,KAAK,EAAE,IAAI,CAAC;QACtH,WAAW,EAAE,EAAE,CAAC,MAAM;QACtB,aAAa,EAAE,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,IAAI,CAAC;QACzD,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;QACpG,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,6CAA6C,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK;QAC9F,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,8EAA8E;QAC9E,qDAAqD;KACrC,CAAC;AACrB,CAAC;AAED,kEAAkE;AAClE,SAAgB,QAAQ;IACtB,OAAO,IAAI,CAAC,GAAG,EAAE;QACf,IAAI,OAAO,YAAY,KAAK,WAAW;YAAE,OAAO,IAAI,CAAC;QACrD,MAAM,GAAG,GAAG,aAAa,CAAC;QAC1B,IAAI,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;YACxF,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC;AAED,sEAAsE;AACtE,SAAgB,iBAAiB,CAAC,MAAqB;IACrD,MAAM,KAAK,GAAG;QACZ,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1B,MAAM,CAAC,WAAW;QAClB,MAAM,CAAC,aAAa;QACpB,MAAM,CAAC,WAAW;QAClB,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,mBAAmB;QAC1B,UAAU,EAAE;KACb,CAAC;IACF,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,yEAAyE;AACzE,SAAS,IAAI,CAAC,CAAS;IACrB,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACjF,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACzC,CAAC;AA2BD,MAAM,IAAI,GAAG,CAAC,EAAY,EAAiB,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7G,MAAM,QAAQ,GAAG,CAAC,EAAY,EAAiB,EAAE;IAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACnB,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC;AAC9D,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAa,gBAAgB;IACV,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,gBAAgB,GAAkB,IAAI,CAAC;IACvC,KAAK,GAAG,CAAC,CAAC;IACV,QAAQ,GAAG,CAAC,CAAC;IACb,QAAQ,GAAG,CAAC,CAAC;IACb,MAAM,GAAa,EAAE,CAAC;IACtB,KAAK,GAAkB,IAAI,CAAC;IAC5B,KAAK,GAAkB,IAAI,CAAC;IAC5B,SAAS,GAAG,CAAC,CAAC;IACd,KAAK,GAAG,CAAC,CAAC;IACV,KAAK,GAAG,CAAC,CAAC;IACV,QAAQ,GAAG,CAAC,CAAC;IACb,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnC,KAAK,GAAa,EAAE,CAAC;IACrB,MAAM,GAAa,EAAE,CAAC;IACtB,QAAQ,GAAkB,IAAI,CAAC;IAC/B,MAAM,GAAG,CAAC,CAAC;IACX,UAAU,GAAG,CAAC,CAAC;IACf,YAAY,GAAG,CAAC,CAAC;IACjB,OAAO,GAAG,CAAC,CAAC;IACZ,MAAM,CAAuB;IAC7B,KAAK,GAAmC,EAAE,CAAC;IAEnD,YAAY,MAAiB;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACjF,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,gBAAgB,KAAK,IAAI;YAAE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;IACtF,CAAC;IAEO,MAAM;QACZ,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAiB,EAAE,EAAE;YAC7C,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAA6B,CAAC,EAAE,SAAS,CAAC,CAAC;YAClG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC;QACF,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAiB,CAAC,CAAC,CAAC;QACzD,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAe,CAAC,CAAC,CAAC;QACrD,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAkB,CAAC,CAAC,CAAC;QACzD,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAkB,CAAC,CAAC,CAAC;QACrD,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACf,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YAChB,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,CAAyC;QACtD,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAC/C,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;YACnB,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;YAChC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;gBACX,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;gBACrB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACxB,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ;oBAAE,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACnD,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;IACvB,CAAC;IAEO,SAAS,CAAC,CAAgB;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW;YAAE,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QAChD,4EAA4E;QAC5E,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACtE,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3E,CAAC;IAEO,OAAO,CAAC,CAAgB;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;IACtB,CAAC;IAED,gEAAgE;IAChE,QAAQ;QACN,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,KAAK;YACtB,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;YACxC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAChC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI;YACtD,kBAAkB,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;YACzC,sBAAsB,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACvF,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAC/B,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACjC,gBAAgB,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;YACtC,iBAAiB,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;YACxC,UAAU,EAAE,IAAI,CAAC,MAAM;YACvB,cAAc,EAAE,IAAI,CAAC,UAAU;YAC/B,wBAAwB,EAAE,IAAI,CAAC,gBAAgB;YAC/C,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;YACvC,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,WAAW,EAAE,IAAI,CAAC,OAAO;YACzB,cAAc,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC;YAC9B,UAAU,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC;YAC7B,gBAAgB,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY;SAChF,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,IAAI;QACF,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QAC5F,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;CACF;AAzID,4CAyIC;AAcD;;;;GAIG;AACI,KAAK,UAAU,aAAa,CAAC,IAAsB;IACxD,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE;QACrB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG;YACd,UAAU,EAAE,IAAI,CAAC,SAAS;YAC1B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,WAAW;YAC9B,SAAS,EAAE,QAAQ,EAAE;YACrB,kBAAkB,EAAE,iBAAiB,CAAC,MAAM,CAAC;YAC7C,MAAM;YACN,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE;SACpC,CAAC;QACF,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,wCAAwC,kBAAkB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QACzG,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAErC,0EAA0E;QAC1E,4EAA4E;QAC5E,0EAA0E;QAC1E,wEAAwE;QACxE,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,UAAU,IAAI,OAAO,IAAI,KAAK,WAAW,EAAE,CAAC;YAC9F,MAAM,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC;YAC7E,IAAI,EAAE;gBAAE,OAAO,IAAI,CAAC;QACtB,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACrF,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;YAC7B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,IAAI,CAAC,cAAc,EAAE;YACzF,IAAI;YACJ,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,SAAS;SACvB,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * §7.4 step 1: "the SDK calls POST /v1/client/sessions/:sid/tokens with the
3
+ * refresh cookie whenever its cached JWT is within 10 s of expiry (proactive,
4
+ * jittered to avoid thundering herds across tabs)."
5
+ *
6
+ * The scheduling is the whole problem, and every parameter here is chosen
7
+ * against a specific failure:
8
+ *
9
+ * Refreshing at expiry rather than before it means every request in the
10
+ * window between "token died" and "new token arrived" gets a 401. Users
11
+ * experience that as random logouts on slow connections.
12
+ *
13
+ * Refreshing with no jitter means twenty tabs wake at the same millisecond
14
+ * and hit the API together — which is fine at ten users and an outage at ten
15
+ * thousand.
16
+ *
17
+ * Jittering too WIDELY is worse than not jittering: a token with 60 seconds
18
+ * of life, refreshed 10 seconds early with ±15 seconds of jitter, sometimes
19
+ * refreshes after it has already expired. The jitter window is therefore
20
+ * bounded to strictly less than the lead time, and there is a test that
21
+ * pins it.
22
+ */
23
+ /** §7.4: refresh once the token is within this of expiring. */
24
+ export declare const REFRESH_LEAD_MS = 10000;
25
+ /**
26
+ * Jitter is subtracted from the delay only, never added. Adding could push the
27
+ * refresh past expiry; subtracting only ever makes it earlier, which is safe.
28
+ */
29
+ export declare const MAX_JITTER_MS = 5000;
30
+ export interface CachedToken {
31
+ jwt: string;
32
+ /** Absolute expiry, epoch ms. */
33
+ expiresAt: number;
34
+ sessionId: string;
35
+ }
36
+ export interface ScheduleInput {
37
+ token: CachedToken;
38
+ now: number;
39
+ leadMs?: number;
40
+ /** Injected so the schedule is deterministic under test. */
41
+ jitter?: () => number;
42
+ }
43
+ /**
44
+ * Milliseconds until this token should be refreshed.
45
+ *
46
+ * Zero means "refresh now" — the token is already inside its lead window, or
47
+ * past expiry. Never negative, because a negative delay in a `setTimeout` is
48
+ * silently treated as zero anyway and the explicit clamp is clearer than
49
+ * relying on that.
50
+ */
51
+ export declare function msUntilRefresh(input: ScheduleInput): number;
52
+ /** Whether the cached token can still be handed to a caller. */
53
+ export declare function isUsable(token: CachedToken | null, now: number, skewMs?: number): boolean;
54
+ /**
55
+ * Read `exp` out of a JWT without verifying it.
56
+ *
57
+ * Deliberately does NOT verify. The client cannot verify — it has no key, and
58
+ * shipping one would be absurd — so this is a scheduling hint and nothing more.
59
+ * Every security decision is made by the server against a signature; a token
60
+ * whose `exp` a hostile party edited only causes the client to refresh at the
61
+ * wrong moment, which the server then rejects on its own terms.
62
+ */
63
+ export declare function readExpiry(jwt: string): number | null;
64
+ export declare class TokenCache {
65
+ private readonly now;
66
+ private token;
67
+ constructor(now?: () => number);
68
+ set(token: CachedToken): void;
69
+ /** The cached token, or null if it is gone or too close to expiry to use. */
70
+ get(skewMs?: number): CachedToken | null;
71
+ /** Present regardless of usability — for deciding whether to refresh or sign in. */
72
+ peek(): CachedToken | null;
73
+ clear(): void;
74
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * §7.4 step 1: "the SDK calls POST /v1/client/sessions/:sid/tokens with the
4
+ * refresh cookie whenever its cached JWT is within 10 s of expiry (proactive,
5
+ * jittered to avoid thundering herds across tabs)."
6
+ *
7
+ * The scheduling is the whole problem, and every parameter here is chosen
8
+ * against a specific failure:
9
+ *
10
+ * Refreshing at expiry rather than before it means every request in the
11
+ * window between "token died" and "new token arrived" gets a 401. Users
12
+ * experience that as random logouts on slow connections.
13
+ *
14
+ * Refreshing with no jitter means twenty tabs wake at the same millisecond
15
+ * and hit the API together — which is fine at ten users and an outage at ten
16
+ * thousand.
17
+ *
18
+ * Jittering too WIDELY is worse than not jittering: a token with 60 seconds
19
+ * of life, refreshed 10 seconds early with ±15 seconds of jitter, sometimes
20
+ * refreshes after it has already expired. The jitter window is therefore
21
+ * bounded to strictly less than the lead time, and there is a test that
22
+ * pins it.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.TokenCache = exports.MAX_JITTER_MS = exports.REFRESH_LEAD_MS = void 0;
26
+ exports.msUntilRefresh = msUntilRefresh;
27
+ exports.isUsable = isUsable;
28
+ exports.readExpiry = readExpiry;
29
+ /** §7.4: refresh once the token is within this of expiring. */
30
+ exports.REFRESH_LEAD_MS = 10_000;
31
+ /**
32
+ * Jitter is subtracted from the delay only, never added. Adding could push the
33
+ * refresh past expiry; subtracting only ever makes it earlier, which is safe.
34
+ */
35
+ exports.MAX_JITTER_MS = 5_000;
36
+ /**
37
+ * Milliseconds until this token should be refreshed.
38
+ *
39
+ * Zero means "refresh now" — the token is already inside its lead window, or
40
+ * past expiry. Never negative, because a negative delay in a `setTimeout` is
41
+ * silently treated as zero anyway and the explicit clamp is clearer than
42
+ * relying on that.
43
+ */
44
+ function msUntilRefresh(input) {
45
+ const lead = input.leadMs ?? exports.REFRESH_LEAD_MS;
46
+ const jitterFn = input.jitter ?? (() => Math.random() * exports.MAX_JITTER_MS);
47
+ // Bounded to strictly less than the lead, so subtracting jitter can never
48
+ // move the refresh past the moment the token actually dies.
49
+ const jitter = Math.min(Math.max(jitterFn(), 0), Math.max(lead - 1, 0));
50
+ const target = input.token.expiresAt - lead - jitter;
51
+ return Math.max(0, target - input.now);
52
+ }
53
+ /** Whether the cached token can still be handed to a caller. */
54
+ function isUsable(token, now, skewMs = 0) {
55
+ if (!token)
56
+ return false;
57
+ return token.expiresAt - skewMs > now;
58
+ }
59
+ /**
60
+ * Read `exp` out of a JWT without verifying it.
61
+ *
62
+ * Deliberately does NOT verify. The client cannot verify — it has no key, and
63
+ * shipping one would be absurd — so this is a scheduling hint and nothing more.
64
+ * Every security decision is made by the server against a signature; a token
65
+ * whose `exp` a hostile party edited only causes the client to refresh at the
66
+ * wrong moment, which the server then rejects on its own terms.
67
+ */
68
+ function readExpiry(jwt) {
69
+ const parts = jwt.split('.');
70
+ if (parts.length !== 3)
71
+ return null;
72
+ try {
73
+ const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
74
+ return typeof payload.exp === 'number' ? payload.exp * 1000 : null;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ class TokenCache {
81
+ now;
82
+ token = null;
83
+ constructor(now = () => Date.now()) {
84
+ this.now = now;
85
+ }
86
+ set(token) {
87
+ this.token = token;
88
+ }
89
+ /** The cached token, or null if it is gone or too close to expiry to use. */
90
+ get(skewMs = 0) {
91
+ if (!isUsable(this.token, this.now(), skewMs))
92
+ return null;
93
+ return this.token;
94
+ }
95
+ /** Present regardless of usability — for deciding whether to refresh or sign in. */
96
+ peek() {
97
+ return this.token;
98
+ }
99
+ clear() {
100
+ this.token = null;
101
+ }
102
+ }
103
+ exports.TokenCache = TokenCache;
104
+ //# sourceMappingURL=token-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../src/token-cache.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAkCH,wCAUC;AAGD,4BAGC;AAWD,gCAYC;AAvED,+DAA+D;AAClD,QAAA,eAAe,GAAG,MAAM,CAAC;AAEtC;;;GAGG;AACU,QAAA,aAAa,GAAG,KAAK,CAAC;AAiBnC;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,KAAoB;IACjD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,uBAAe,CAAC;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,qBAAa,CAAC,CAAC;IAEvE,0EAA0E;IAC1E,4DAA4D;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,GAAG,MAAM,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,gEAAgE;AAChE,SAAgB,QAAQ,CAAC,KAAyB,EAAE,GAAW,EAAE,MAAM,GAAG,CAAC;IACzE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,KAAK,CAAC,SAAS,GAAG,MAAM,GAAG,GAAG,CAAC;AACxC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAE/E,CAAC;QACF,OAAO,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAa,UAAU;IAGQ;IAFrB,KAAK,GAAuB,IAAI,CAAC;IAEzC,YAA6B,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QAApC,QAAG,GAAH,GAAG,CAAiC;IAAG,CAAC;IAErE,GAAG,CAAC,KAAkB;QACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,6EAA6E;IAC7E,GAAG,CAAC,MAAM,GAAG,CAAC;QACZ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3D,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,oFAAoF;IACpF,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF;AAvBD,gCAuBC"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@atlasauth/js",
3
+ "version": "0.1.0",
4
+ "main": "./dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "devDependencies": {
7
+ "happy-dom": "^15.11.0",
8
+ "typescript": "^5.6.3",
9
+ "vitest": "^2.1.4"
10
+ },
11
+ "dependencies": {
12
+ "@atlasauth/authz": "0.1.0"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run"
25
+ }
26
+ }