@pelican-identity/auth-core 1.2.7 → 1.2.9

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/dist/index.js CHANGED
@@ -1,514 +1,8 @@
1
- 'use strict';
2
-
3
- var nacl = require('tweetnacl');
4
- var tweetnaclUtil = require('tweetnacl-util');
5
- var QRCode = require('qrcode');
6
-
7
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
-
9
- var nacl__default = /*#__PURE__*/_interopDefault(nacl);
10
- var QRCode__default = /*#__PURE__*/_interopDefault(QRCode);
11
-
12
- // src/utilities/crypto.ts
13
- var CryptoService = class {
14
- generateSymmetricKey() {
15
- const key = nacl__default.default.randomBytes(32);
16
- return tweetnaclUtil.encodeBase64(key);
17
- }
18
- /**
19
- * Encrypt with symmetric key (secret-key encryption)
20
- * Use this when both parties share the same secret key
21
- * @param plaintext - Message to encrypt
22
- * @param keyString - Symmetric key (base64)
23
- * @returns Encrypted message with nonce
24
- */
25
- encryptSymmetric({
26
- plaintext,
27
- keyString
28
- }) {
29
- const key = tweetnaclUtil.decodeBase64(keyString);
30
- const nonce = nacl__default.default.randomBytes(24);
31
- const messageBytes = new TextEncoder().encode(plaintext);
32
- const ciphertext = nacl__default.default.secretbox(messageBytes, nonce, key);
33
- return {
34
- cipher: tweetnaclUtil.encodeBase64(ciphertext),
35
- nonce: tweetnaclUtil.encodeBase64(nonce)
36
- };
37
- }
38
- /**
39
- * Decrypt with symmetric key
40
- * @param encrypted - Encrypted message with nonce
41
- * @param keyString - Symmetric key (base64)
42
- * @returns Decrypted plaintext
43
- */
44
- decryptSymmetric({
45
- encrypted,
46
- keyString
47
- }) {
48
- try {
49
- const key = tweetnaclUtil.decodeBase64(keyString);
50
- const ciphertextBytes = tweetnaclUtil.decodeBase64(encrypted.cipher);
51
- const nonceBytes = tweetnaclUtil.decodeBase64(encrypted.nonce);
52
- const decrypted = nacl__default.default.secretbox.open(ciphertextBytes, nonceBytes, key);
53
- if (!decrypted) {
54
- throw new Error("Decryption failed - invalid key or corrupted data");
55
- }
56
- const decoded = new TextDecoder().decode(decrypted);
57
- return decoded;
58
- } catch (error) {
59
- console.error("Decryption failed", error);
60
- return null;
61
- }
62
- }
63
- };
64
- var crypto_default = CryptoService;
65
-
66
- // src/utilities/storage.ts
67
- var AuthStorage = class {
68
- constructor() {
69
- this.prefix = "pelican_auth_";
70
- this.defaultTTL = 5 * 60 * 1e3;
71
- // 5 minutes
72
- this.memoryCache = /* @__PURE__ */ new Map();
73
- }
74
- /**
75
- * Store auth session with automatic cleanup
76
- */
77
- set(key, value, options = {}) {
78
- const { ttlMs = this.defaultTTL, useSessionStorage = true } = options;
79
- const expiresAt = Date.now() + ttlMs;
80
- const data = { value, expiresAt };
81
- if (useSessionStorage) {
82
- try {
83
- sessionStorage.setItem(`${this.prefix}${key}`, JSON.stringify(data));
84
- } catch (error) {
85
- console.warn("SessionStorage unavailable, using memory:", error);
86
- this.setMemory(key, data);
87
- }
88
- } else {
89
- this.setMemory(key, data);
90
- }
91
- }
92
- /**
93
- * Get stored value if not expired
94
- */
95
- get(key) {
96
- try {
97
- const stored = sessionStorage.getItem(`${this.prefix}${key}`);
98
- if (stored) {
99
- const data = JSON.parse(stored);
100
- if (Date.now() > data.expiresAt) {
101
- this.remove(key);
102
- return null;
103
- }
104
- return data.value;
105
- }
106
- } catch (error) {
107
- }
108
- return this.getMemory(key);
109
- }
110
- /**
111
- * Remove specific key
112
- */
113
- remove(key) {
114
- try {
115
- sessionStorage.removeItem(`${this.prefix}${key}`);
116
- } catch (error) {
117
- }
118
- this.memoryCache.delete(key);
119
- }
120
- /**
121
- * Clear all auth data
122
- */
123
- clear() {
124
- try {
125
- Object.keys(sessionStorage).forEach((key) => {
126
- if (key.startsWith(this.prefix)) {
127
- sessionStorage.removeItem(key);
128
- }
129
- });
130
- } catch (error) {
131
- }
132
- this.memoryCache.clear();
133
- }
134
- // ========================================
135
- // Memory cache helpers
136
- // ========================================
137
- setMemory(key, data) {
138
- this.memoryCache.set(key, data);
139
- const ttl = data.expiresAt - Date.now();
140
- setTimeout(() => {
141
- this.memoryCache.delete(key);
142
- }, ttl);
143
- }
144
- getMemory(key) {
145
- const data = this.memoryCache.get(key);
146
- if (!data) return null;
147
- if (Date.now() > data.expiresAt) {
148
- this.memoryCache.delete(key);
149
- return null;
150
- }
151
- return data.value;
152
- }
153
- };
154
- var storage = new AuthStorage();
155
- var storeAuthSession = (sessionId, sessionKey, ttlMs) => {
156
- storage.set("session", { sessionId, sessionKey }, { ttlMs });
157
- };
158
- var getAuthSession = () => {
159
- return storage.get("session");
160
- };
161
- var clearAuthSession = () => {
162
- storage.remove("session");
163
- };
164
- var clearAllAuthData = () => {
165
- storage.clear();
166
- };
167
-
168
- // src/utilities/stateMachine.ts
169
- var StateMachine = class {
170
- constructor() {
171
- this.state = "idle";
172
- this.listeners = /* @__PURE__ */ new Set();
173
- }
174
- get current() {
175
- return this.state;
176
- }
177
- transition(next) {
178
- this.state = next;
179
- this.listeners.forEach((l) => l(next));
180
- }
181
- subscribe(fn) {
182
- this.listeners.add(fn);
183
- return () => this.listeners.delete(fn);
184
- }
185
- };
186
-
187
- // src/utilities/transport.ts
188
- var Transport = class {
189
- constructor(handlers) {
190
- this.reconnectAttempts = 0;
191
- this.maxReconnectAttempts = 5;
192
- this.isExplicitlyClosed = false;
193
- this.handlers = handlers;
194
- }
195
- connect(url) {
196
- this.url = url;
197
- this.isExplicitlyClosed = false;
198
- this.socket = new WebSocket(url);
199
- this.socket.onopen = () => {
200
- this.reconnectAttempts = 0;
201
- this.handlers.onOpen?.();
202
- };
203
- this.socket.onmessage = (e) => {
204
- try {
205
- const data = JSON.parse(e.data);
206
- this.handlers.onMessage?.(data);
207
- } catch (err) {
208
- console.error("Failed to parse message", err);
209
- }
210
- };
211
- this.socket.onerror = (e) => {
212
- this.handlers.onError?.(e);
213
- };
214
- this.socket.onclose = (e) => {
215
- this.handlers.onClose?.(e);
216
- if (!this.isExplicitlyClosed) {
217
- this.attemptReconnect();
218
- }
219
- };
220
- }
221
- attemptReconnect() {
222
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
223
- console.error("\u274C Max reconnect attempts reached");
224
- return;
225
- }
226
- this.reconnectAttempts++;
227
- const delay = Math.pow(2, this.reconnectAttempts) * 500;
228
- setTimeout(() => {
229
- if (this.url) this.connect(this.url);
230
- }, delay);
231
- }
232
- send(payload) {
233
- if (this.socket?.readyState === WebSocket.OPEN) {
234
- this.socket.send(JSON.stringify(payload));
235
- }
236
- }
237
- close() {
238
- this.isExplicitlyClosed = true;
239
- this.socket?.close();
240
- }
241
- };
242
-
243
- // src/constants.ts
244
- var BASEURL = "http://192.168.1.2:8080";
245
-
246
- // src/engine/engine.ts
247
- var PelicanAuthentication = class {
248
- constructor(config) {
249
- this.crypto = new crypto_default();
250
- this.stateMachine = new StateMachine();
251
- this.sessionId = "";
252
- this.sessionKey = null;
253
- this.listeners = {};
254
- if (!config.publicKey) throw new Error("Missing publicKey");
255
- if (!config.projectId) throw new Error("Missing projectId");
256
- if (!config.authType) throw new Error("Missing authType");
257
- this.config = {
258
- continuousMode: false,
259
- forceQRCode: false,
260
- ...config
261
- };
262
- this.stateMachine.subscribe((s) => this.emit("state", s));
263
- this.attachVisibilityRecovery();
264
- }
265
- /* -------------------- public API -------------------- */
266
- /**
267
- * Subscribe to SDK events (qr, deeplink, success, error, state)
268
- * @returns Unsubscribe function
269
- */
270
- on(event, cb) {
271
- var _a;
272
- (_a = this.listeners)[event] ?? (_a[event] = /* @__PURE__ */ new Set());
273
- this.listeners[event].add(cb);
274
- return () => this.listeners[event].delete(cb);
275
- }
276
- /**
277
- * Initializes the authentication flow.
278
- * Fetches a relay, establishes a WebSocket, and generates the E2EE session key.
279
- */
280
- async start() {
281
- if (this.stateMachine.current !== "idle") return;
282
- this.resetSession();
283
- this.stateMachine.transition("initializing");
284
- try {
285
- const relay = await this.fetchRelayUrl();
286
- this.sessionKey = this.crypto.generateSymmetricKey();
287
- this.sessionId = crypto.randomUUID() + crypto.randomUUID();
288
- this.transport = new Transport({
289
- onOpen: () => {
290
- this.transport.send({
291
- type: "register",
292
- sessionID: this.sessionId,
293
- ...this.config
294
- });
295
- this.stateMachine.transition("awaiting-pair");
296
- },
297
- onMessage: (msg) => this.handleMessage(msg),
298
- onError: () => this.fail(new Error("WebSocket connection failed"))
299
- });
300
- this.transport.connect(relay);
301
- await this.emitEntryPoint();
302
- } catch (err) {
303
- this.fail(err instanceof Error ? err : new Error("Start failed"));
304
- }
305
- }
306
- /**
307
- * Manually stops the authentication process and closes connections.
308
- */
309
- stop() {
310
- this.terminate(false);
311
- }
312
- /* -------------------- internals -------------------- */
313
- /** Fetches the WebSocket Relay URL from the backend */
314
- async fetchRelayUrl() {
315
- const { publicKey, projectId, authType } = this.config;
316
- const res = await fetch(
317
- `${BASEURL}/relay?public_key=${publicKey}&auth_type=${authType}&project_id=${projectId}`
318
- );
319
- if (!res.ok) {
320
- throw new Error("Failed to fetch relay URL");
321
- }
322
- const json = await res.json();
323
- return json.relay_url;
324
- }
325
- /**
326
- * Decides whether to show a QR code (Desktop) or a Deep Link (Mobile).
327
- */
328
- async emitEntryPoint() {
329
- const payload = {
330
- sessionID: this.sessionId,
331
- sessionKey: this.sessionKey,
332
- publicKey: this.config.publicKey,
333
- authType: this.config.authType,
334
- projectId: this.config.projectId,
335
- url: window.location.href
336
- };
337
- const shouldUseQR = this.config.forceQRCode || !/Android|iPhone|iPad/i.test(navigator.userAgent);
338
- if (!shouldUseQR && this.sessionKey) {
339
- storeAuthSession(this.sessionId, this.sessionKey, 5 * 6e4);
340
- this.emit(
341
- "deeplink",
342
- `pelicanvault://auth/deep-link?sessionID=${encodeURIComponent(
343
- this.sessionId
344
- )}&sessionKey=${encodeURIComponent(
345
- this.sessionKey
346
- )}&publicKey=${encodeURIComponent(this.config.publicKey)}&authType=${this.config.authType}&projectId=${encodeURIComponent(
347
- this.config.projectId
348
- )}&url=${encodeURIComponent(window.location.href)}`
349
- );
350
- } else {
351
- const qr = await QRCode__default.default.toDataURL(JSON.stringify(payload), {
352
- type: "image/png",
353
- scale: 3,
354
- color: { light: "#FFFFF5", dark: "#121212" }
355
- });
356
- this.emit("qr", qr);
357
- }
358
- }
359
- /** Main WebSocket message router */
360
- handleMessage(msg) {
361
- switch (msg.type) {
362
- case "paired":
363
- this.stateMachine.transition("paired");
364
- this.transport.send({
365
- type: "authenticate",
366
- sessionID: this.sessionId,
367
- ...this.config,
368
- url: window.location.href
369
- });
370
- return;
371
- case "phone-auth-success":
372
- this.handleAuthSuccess(msg);
373
- return;
374
- case "phone-terminated":
375
- this.fail(new Error("Authenticating device terminated the connection"));
376
- this.restartIfContinuous();
377
- return;
378
- case "confirmed":
379
- this.terminate(true);
380
- this.restartIfContinuous();
381
- return;
382
- }
383
- }
384
- /**
385
- * Decrypts the identity payload received from the phone using the session key.
386
- */
387
- handleAuthSuccess(msg) {
388
- if (!this.sessionKey || !msg.cipher || !msg.nonce) {
389
- this.fail(new Error("Invalid authentication payload"));
390
- this.restartIfContinuous();
391
- return;
392
- }
393
- try {
394
- const decrypted = this.crypto.decryptSymmetric({
395
- encrypted: { cipher: msg.cipher, nonce: msg.nonce },
396
- keyString: this.sessionKey
397
- });
398
- if (!decrypted) {
399
- this.fail(new Error("Invalid authentication data"));
400
- this.restartIfContinuous();
401
- return;
402
- }
403
- const result = JSON.parse(decrypted);
404
- this.emit("success", result);
405
- this.stateMachine.transition("authenticated");
406
- this.transport?.send({
407
- type: "confirm",
408
- sessionID: this.sessionId
409
- });
410
- } catch {
411
- this.fail(new Error("Failed to decrypt authentication data"));
412
- this.restartIfContinuous();
413
- }
414
- }
415
- /**
416
- * Logic to handle users returning to the browser tab after using the mobile app.
417
- * Checks the server for a completed session that might have finished while in background.
418
- */
419
- async getCachedEntry(cached) {
420
- try {
421
- const res = await fetch(
422
- `${BASEURL}/session?session_id=${cached.sessionId}`
423
- );
424
- if (!res.ok) throw new Error("Invalid session");
425
- const data = await res.json();
426
- const decrypted = this.crypto.decryptSymmetric({
427
- encrypted: { cipher: data.cipher, nonce: data.nonce },
428
- keyString: cached.sessionKey
429
- });
430
- if (!decrypted) {
431
- this.fail(new Error("Invalid session data"));
432
- this.restartIfContinuous();
433
- return;
434
- }
435
- const result = JSON.parse(decrypted);
436
- this.emit("success", result);
437
- clearAuthSession();
438
- } catch {
439
- this.fail(new Error("Session recovery failed"));
440
- this.restartIfContinuous();
441
- }
442
- }
443
- attachVisibilityRecovery() {
444
- this.visibilityHandler = async () => {
445
- if (document.visibilityState !== "visible") return;
446
- const cached = getAuthSession();
447
- if (!cached) return;
448
- await this.getCachedEntry(cached);
449
- };
450
- document.addEventListener("visibilitychange", this.visibilityHandler);
451
- }
452
- detachVisibilityRecovery() {
453
- if (this.visibilityHandler) {
454
- document.removeEventListener("visibilitychange", this.visibilityHandler);
455
- this.visibilityHandler = void 0;
456
- }
457
- }
458
- /** Cleans up the current session state */
459
- terminate(success) {
460
- if (!this.transport) return;
461
- if (!success) {
462
- this.transport?.send({
463
- type: "client-terminated",
464
- sessionID: this.sessionId,
465
- projectId: this.config.projectId,
466
- publicKey: this.config.publicKey,
467
- authType: this.config.authType
468
- });
469
- }
470
- this.transport?.close();
471
- clearAuthSession();
472
- this.resetSession();
473
- if (!this.config.continuousMode) {
474
- this.detachVisibilityRecovery();
475
- }
476
- this.stateMachine.transition(success ? "confirmed" : "idle");
477
- }
478
- restartIfContinuous() {
479
- this.stateMachine.transition("idle");
480
- if (!this.config.continuousMode) return;
481
- setTimeout(() => {
482
- this.start();
483
- }, 150);
484
- }
485
- resetSession() {
486
- this.sessionId = "";
487
- this.sessionKey = null;
488
- }
489
- /** Completely destroys the instance and removes all listeners */
490
- destroy() {
491
- this.detachVisibilityRecovery();
492
- this.transport?.close();
493
- this.listeners = {};
494
- }
495
- emit(event, payload) {
496
- this.listeners[event]?.forEach((cb) => cb(payload));
497
- }
498
- fail(err) {
499
- this.emit("error", err);
500
- this.stateMachine.transition("error");
501
- }
502
- };
503
-
504
- exports.BASEURL = BASEURL;
505
- exports.CryptoService = CryptoService;
506
- exports.PelicanAuthentication = PelicanAuthentication;
507
- exports.StateMachine = StateMachine;
508
- exports.Transport = Transport;
509
- exports.clearAllAuthData = clearAllAuthData;
510
- exports.clearAuthSession = clearAuthSession;
511
- exports.getAuthSession = getAuthSession;
512
- exports.storeAuthSession = storeAuthSession;
513
- //# sourceMappingURL=index.js.map
1
+ export * from "./types/types";
2
+ export { PelicanAuthentication } from "./engine/engine";
3
+ export { CryptoService } from "./utilities/crypto";
4
+ export { StateMachine } from "./utilities/stateMachine";
5
+ export { Transport } from "./utilities/transport";
6
+ export * from "./utilities/storage";
7
+ export { BASEURL } from "./constants";
514
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utilities/crypto.ts","../src/utilities/storage.ts","../src/utilities/stateMachine.ts","../src/utilities/transport.ts","../src/constants.ts","../src/engine/engine.ts"],"names":["nacl","encodeBase64","decodeBase64","QRCode"],"mappings":";;;;;;;;;;;;AAQO,IAAM,gBAAN,MAAoB;AAAA,EACzB,oBAAA,GAA+B;AAC7B,IAAA,MAAM,GAAA,GAAMA,qBAAA,CAAK,WAAA,CAAY,EAAE,CAAA;AAC/B,IAAA,OAAOC,2BAAa,GAAG,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,gBAAA,CAAiB;AAAA,IACf,SAAA;AAAA,IACA;AAAA,GACF,EAGqB;AACnB,IAAA,MAAM,GAAA,GAAMC,2BAAa,SAAS,CAAA;AAClC,IAAA,MAAM,KAAA,GAAQF,qBAAA,CAAK,WAAA,CAAY,EAAE,CAAA;AAEjC,IAAA,MAAM,YAAA,GAAe,IAAI,WAAA,EAAY,CAAE,OAAO,SAAS,CAAA;AAEvD,IAAA,MAAM,UAAA,GAAaA,qBAAA,CAAK,SAAA,CAAU,YAAA,EAAc,OAAO,GAAG,CAAA;AAE1D,IAAA,OAAO;AAAA,MACL,MAAA,EAAQC,2BAAa,UAAU,CAAA;AAAA,MAC/B,KAAA,EAAOA,2BAAa,KAAK;AAAA,KAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAA,CAAiB;AAAA,IACf,SAAA;AAAA,IACA;AAAA,GACF,EAGkB;AAChB,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAMC,2BAAa,SAAS,CAAA;AAClC,MAAA,MAAM,eAAA,GAAkBA,0BAAA,CAAa,SAAA,CAAU,MAAM,CAAA;AACrD,MAAA,MAAM,UAAA,GAAaA,0BAAA,CAAa,SAAA,CAAU,KAAK,CAAA;AAE/C,MAAA,MAAM,YAAYF,qBAAA,CAAK,SAAA,CAAU,IAAA,CAAK,eAAA,EAAiB,YAAY,GAAG,CAAA;AAEtE,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,MACrE;AAEA,MAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY,CAAE,OAAO,SAAS,CAAA;AAClD,MAAA,OAAO,OAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,qBAAqB,KAAK,CAAA;AACxC,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AACF;AAEA,IAAO,cAAA,GAAQ,aAAA;;;AC/Df,IAAM,cAAN,MAAkB;AAAA,EAAlB,WAAA,GAAA;AACE,IAAA,IAAA,CAAiB,MAAA,GAAS,eAAA;AAC1B,IAAA,IAAA,CAAiB,UAAA,GAAa,IAAI,EAAA,GAAK,GAAA;AACvC;AAAA,IAAA,IAAA,CAAQ,WAAA,uBACF,GAAA,EAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAKV,GAAA,CAAI,GAAA,EAAa,KAAA,EAAY,OAAA,GAA0B,EAAC,EAAS;AAC/D,IAAA,MAAM,EAAE,KAAA,GAAQ,IAAA,CAAK,UAAA,EAAY,iBAAA,GAAoB,MAAK,GAAI,OAAA;AAE9D,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA;AAC/B,IAAA,MAAM,IAAA,GAAO,EAAE,KAAA,EAAO,SAAA,EAAU;AAGhC,IAAA,IAAI,iBAAA,EAAmB;AACrB,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA;AAAA,MACrE,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,IAAA,CAAK,6CAA6C,KAAK,CAAA;AAC/D,QAAA,IAAA,CAAK,SAAA,CAAU,KAAK,IAAI,CAAA;AAAA,MAC1B;AAAA,IACF,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,SAAA,CAAU,KAAK,IAAI,CAAA;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,GAAA,EAAyB;AAE3B,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,eAAe,OAAA,CAAQ,CAAA,EAAG,KAAK,MAAM,CAAA,EAAG,GAAG,CAAA,CAAE,CAAA;AAC5D,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAG9B,QAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,SAAA,EAAW;AAC/B,UAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AACf,UAAA,OAAO,IAAA;AAAA,QACT;AAEA,QAAA,OAAO,IAAA,CAAK,KAAA;AAAA,MACd;AAAA,IACF,SAAS,KAAA,EAAO;AAAA,IAEhB;AAGA,IAAA,OAAO,IAAA,CAAK,UAAU,GAAG,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,GAAA,EAAmB;AACxB,IAAA,IAAI;AACF,MAAA,cAAA,CAAe,WAAW,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,EAAG,GAAG,CAAA,CAAE,CAAA;AAAA,IAClD,SAAS,KAAA,EAAO;AAAA,IAEhB;AACA,IAAA,IAAA,CAAK,WAAA,CAAY,OAAO,GAAG,CAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AAEZ,IAAA,IAAI;AACF,MAAA,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA,CAAE,OAAA,CAAQ,CAAC,GAAA,KAAQ;AAC3C,QAAA,IAAI,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA,EAAG;AAC/B,UAAA,cAAA,CAAe,WAAW,GAAG,CAAA;AAAA,QAC/B;AAAA,MACF,CAAC,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AAAA,IAEhB;AAGA,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMQ,SAAA,CACN,KACA,IAAA,EACM;AACN,IAAA,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAG9B,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI;AACtC,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,IAAA,CAAK,WAAA,CAAY,OAAO,GAAG,CAAA;AAAA,IAC7B,GAAG,GAAG,CAAA;AAAA,EACR;AAAA,EAEQ,UAAU,GAAA,EAAyB;AACzC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AACrC,IAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,SAAA,EAAW;AAC/B,MAAA,IAAA,CAAK,WAAA,CAAY,OAAO,GAAG,CAAA;AAC3B,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF,CAAA;AAGA,IAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAWzB,IAAM,gBAAA,GAAmB,CAC9B,SAAA,EACA,UAAA,EACA,KAAA,KACS;AACT,EAAA,OAAA,CAAQ,GAAA,CAAI,WAAW,EAAE,SAAA,EAAW,YAAW,EAAG,EAAE,OAAO,CAAA;AAC7D;AAEO,IAAM,iBAAiB,MAA0B;AACtD,EAAA,OAAO,OAAA,CAAQ,IAAI,SAAS,CAAA;AAC9B;AAEO,IAAM,mBAAmB,MAAY;AAC1C,EAAA,OAAA,CAAQ,OAAO,SAAS,CAAA;AAC1B;AAEO,IAAM,mBAAmB,MAAY;AAC1C,EAAA,OAAA,CAAQ,KAAA,EAAM;AAChB;;;AC1JO,IAAM,eAAN,MAAmB;AAAA,EAAnB,WAAA,GAAA;AACL,IAAA,IAAA,CAAQ,KAAA,GAA0B,MAAA;AAClC,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAmC;AAAA,EAAA;AAAA,EAE3D,IAAI,OAAA,GAAU;AACZ,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,WAAW,IAAA,EAAwB;AACjC,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AACb,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,EACvC;AAAA,EAEA,UAAU,EAAA,EAAmC;AAC3C,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,EAAE,CAAA;AACrB,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,EAAE,CAAA;AAAA,EACvC;AACF;;;ACVO,IAAM,YAAN,MAAgB;AAAA,EAQrB,YAAY,QAAA,EAA6B;AALzC,IAAA,IAAA,CAAQ,iBAAA,GAAoB,CAAA;AAC5B,IAAA,IAAA,CAAQ,oBAAA,GAAuB,CAAA;AAC/B,IAAA,IAAA,CAAQ,kBAAA,GAAqB,KAAA;AAI3B,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AAAA,EAEA,QAAQ,GAAA,EAAa;AACnB,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,kBAAA,GAAqB,KAAA;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,SAAA,CAAU,GAAG,CAAA;AAE/B,IAAA,IAAA,CAAK,MAAA,CAAO,SAAS,MAAM;AACzB,MAAA,IAAA,CAAK,iBAAA,GAAoB,CAAA;AACzB,MAAA,IAAA,CAAK,SAAS,MAAA,IAAS;AAAA,IACzB,CAAA;AACA,IAAA,IAAA,CAAK,MAAA,CAAO,SAAA,GAAY,CAAC,CAAA,KAAoB;AAC3C,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAA,CAAE,IAAI,CAAA;AAC9B,QAAA,IAAA,CAAK,QAAA,CAAS,YAAY,IAAI,CAAA;AAAA,MAChC,SAAS,GAAA,EAAK;AACZ,QAAA,OAAA,CAAQ,KAAA,CAAM,2BAA2B,GAAG,CAAA;AAAA,MAC9C;AAAA,IACF,CAAA;AACA,IAAA,IAAA,CAAK,MAAA,CAAO,OAAA,GAAU,CAAC,CAAA,KAAa;AAClC,MAAA,IAAA,CAAK,QAAA,CAAS,UAAU,CAAC,CAAA;AAAA,IAC3B,CAAA;AACA,IAAA,IAAA,CAAK,MAAA,CAAO,OAAA,GAAU,CAAC,CAAA,KAAkB;AACvC,MAAA,IAAA,CAAK,QAAA,CAAS,UAAU,CAAC,CAAA;AAGzB,MAAA,IAAI,CAAC,KAAK,kBAAA,EAAoB;AAC5B,QAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,MACxB;AAAA,IACF,CAAA;AAAA,EACF;AAAA,EAEQ,gBAAA,GAAmB;AACzB,IAAA,IAAI,IAAA,CAAK,iBAAA,IAAqB,IAAA,CAAK,oBAAA,EAAsB;AACvD,MAAA,OAAA,CAAQ,MAAM,uCAAkC,CAAA;AAChD,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,iBAAA,EAAA;AAEL,IAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,iBAAiB,CAAA,GAAI,GAAA;AACpD,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,IAAI,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,IACrC,GAAG,KAAK,CAAA;AAAA,EACV;AAAA,EACA,KAAK,OAAA,EAAyB;AAC5B,IAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,UAAA,KAAe,SAAA,CAAU,IAAA,EAAM;AAC9C,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,KAAA,GAAQ;AACN,IAAA,IAAA,CAAK,kBAAA,GAAqB,IAAA;AAC1B,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,EACrB;AACF;;;AC1EO,IAAM,OAAA,GAAU;;;ACyBhB,IAAM,wBAAN,MAA4B;AAAA,EAgBjC,YAAY,MAAA,EAA2B;AAfvC,IAAA,IAAA,CAAiB,MAAA,GAAS,IAAI,cAAA,EAAc;AAC5C,IAAA,IAAA,CAAiB,YAAA,GAAe,IAAI,YAAA,EAAa;AAGjD,IAAA,IAAA,CAAQ,SAAA,GAAY,EAAA;AACpB,IAAA,IAAA,CAAQ,UAAA,GAA4B,IAAA;AAIpC,IAAA,IAAA,CAAQ,YAEJ,EAAC;AAKH,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,EAAW,MAAM,IAAI,MAAM,mBAAmB,CAAA;AAC1D,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,EAAW,MAAM,IAAI,MAAM,mBAAmB,CAAA;AAC1D,IAAA,IAAI,CAAC,MAAA,CAAO,QAAA,EAAU,MAAM,IAAI,MAAM,kBAAkB,CAAA;AAExD,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,cAAA,EAAgB,KAAA;AAAA,MAChB,WAAA,EAAa,KAAA;AAAA,MACb,GAAG;AAAA,KACL;AAGA,IAAA,IAAA,CAAK,YAAA,CAAa,UAAU,CAAC,CAAA,KAAM,KAAK,IAAA,CAAK,OAAA,EAAS,CAAC,CAAC,CAAA;AACxD,IAAA,IAAA,CAAK,wBAAA,EAAyB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,EAAA,CACE,OACA,EAAA,EACA;AAlEJ,IAAA,IAAA,EAAA;AAmEI,IAAA,CAAA,EAAA,GAAA,IAAA,CAAK,SAAA,EAAL,KAAA,CAAA,KAAA,EAAA,CAAA,KAAA,CAAA,mBAA0B,IAAI,GAAA,EAAI,CAAA;AAClC,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,CAAG,GAAA,CAAI,EAAE,CAAA;AAC7B,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,CAAG,OAAO,EAAE,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAA,GAAQ;AACZ,IAAA,IAAI,IAAA,CAAK,YAAA,CAAa,OAAA,KAAY,MAAA,EAAQ;AAE1C,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,YAAA,CAAa,WAAW,cAAc,CAAA;AAE3C,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,aAAA,EAAc;AAGvC,MAAA,IAAA,CAAK,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,oBAAA,EAAqB;AACnD,MAAA,IAAA,CAAK,SAAA,GAAY,MAAA,CAAO,UAAA,EAAW,GAAI,OAAO,UAAA,EAAW;AAEzD,MAAA,IAAA,CAAK,SAAA,GAAY,IAAI,SAAA,CAAU;AAAA,QAC7B,QAAQ,MAAM;AAEZ,UAAA,IAAA,CAAK,UAAW,IAAA,CAAK;AAAA,YACnB,IAAA,EAAM,UAAA;AAAA,YACN,WAAW,IAAA,CAAK,SAAA;AAAA,YAChB,GAAG,IAAA,CAAK;AAAA,WACT,CAAA;AACD,UAAA,IAAA,CAAK,YAAA,CAAa,WAAW,eAAe,CAAA;AAAA,QAC9C,CAAA;AAAA,QACA,SAAA,EAAW,CAAC,GAAA,KAAwB,IAAA,CAAK,cAAc,GAAG,CAAA;AAAA,QAC1D,SAAS,MAAM,IAAA,CAAK,KAAK,IAAI,KAAA,CAAM,6BAA6B,CAAC;AAAA,OAClE,CAAA;AAED,MAAA,IAAA,CAAK,SAAA,CAAU,QAAQ,KAAK,CAAA;AAC5B,MAAA,MAAM,KAAK,cAAA,EAAe;AAAA,IAC5B,SAAS,GAAA,EAAK;AACZ,MAAA,IAAA,CAAK,KAAK,GAAA,YAAe,KAAA,GAAQ,MAAM,IAAI,KAAA,CAAM,cAAc,CAAC,CAAA;AAAA,IAClE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAA,GAAO;AACL,IAAA,IAAA,CAAK,UAAU,KAAK,CAAA;AAAA,EACtB;AAAA;AAAA;AAAA,EAKA,MAAc,aAAA,GAAiC;AAC7C,IAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAW,QAAA,KAAa,IAAA,CAAK,MAAA;AAChD,IAAA,MAAM,MAAM,MAAM,KAAA;AAAA,MAChB,GAAG,OAAO,CAAA,kBAAA,EAAqB,SAAS,CAAA,WAAA,EAAc,QAAQ,eAAe,SAAS,CAAA;AAAA,KACxF;AAEA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAAA,IAC7C;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,OAAO,IAAA,CAAK,SAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAA,GAAiB;AAC7B,IAAA,MAAM,OAAA,GAAU;AAAA,MACd,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,SAAA,EAAW,KAAK,MAAA,CAAO,SAAA;AAAA,MACvB,QAAA,EAAU,KAAK,MAAA,CAAO,QAAA;AAAA,MACtB,SAAA,EAAW,KAAK,MAAA,CAAO,SAAA;AAAA,MACvB,GAAA,EAAK,OAAO,QAAA,CAAS;AAAA,KACvB;AAEA,IAAA,MAAM,WAAA,GACJ,KAAK,MAAA,CAAO,WAAA,IACZ,CAAC,sBAAA,CAAuB,IAAA,CAAK,UAAU,SAAS,CAAA;AAElD,IAAA,IAAI,CAAC,WAAA,IAAe,IAAA,CAAK,UAAA,EAAY;AAEnC,MAAA,gBAAA,CAAiB,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,UAAA,EAAY,IAAI,GAAM,CAAA;AAC5D,MAAA,IAAA,CAAK,IAAA;AAAA,QACH,UAAA;AAAA,QACA,CAAA,wCAAA,EAA2C,kBAAA;AAAA,UACzC,IAAA,CAAK;AAAA,SACN,CAAA,YAAA,EAAe,kBAAA;AAAA,UACd,IAAA,CAAK;AAAA,SACN,CAAA,WAAA,EAAc,kBAAA,CAAmB,IAAA,CAAK,MAAA,CAAO,SAAS,CAAC,CAAA,UAAA,EACtD,IAAA,CAAK,MAAA,CAAO,QACd,CAAA,WAAA,EAAc,kBAAA;AAAA,UACZ,KAAK,MAAA,CAAO;AAAA,SACb,CAAA,KAAA,EAAQ,kBAAA,CAAmB,MAAA,CAAO,QAAA,CAAS,IAAI,CAAC,CAAA;AAAA,OACnD;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAM,KAAK,MAAMG,uBAAA,CAAO,UAAU,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA,EAAG;AAAA,QACzD,IAAA,EAAM,WAAA;AAAA,QACN,KAAA,EAAO,CAAA;AAAA,QACP,KAAA,EAAO,EAAE,KAAA,EAAO,SAAA,EAAW,MAAM,SAAA;AAAU,OAC5C,CAAA;AACD,MAAA,IAAA,CAAK,IAAA,CAAK,MAAM,EAAE,CAAA;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAGQ,cAAc,GAAA,EAAqB;AACzC,IAAA,QAAQ,IAAI,IAAA;AAAM,MAChB,KAAK,QAAA;AACH,QAAA,IAAA,CAAK,YAAA,CAAa,WAAW,QAAQ,CAAA;AACrC,QAAA,IAAA,CAAK,UAAW,IAAA,CAAK;AAAA,UACnB,IAAA,EAAM,cAAA;AAAA,UACN,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAG,IAAA,CAAK,MAAA;AAAA,UACR,GAAA,EAAK,OAAO,QAAA,CAAS;AAAA,SACtB,CAAA;AACD,QAAA;AAAA,MAEF,KAAK,oBAAA;AAEH,QAAA,IAAA,CAAK,kBAAkB,GAAG,CAAA;AAC1B,QAAA;AAAA,MAEF,KAAK,kBAAA;AACH,QAAA,IAAA,CAAK,IAAA,CAAK,IAAI,KAAA,CAAM,iDAAiD,CAAC,CAAA;AACtE,QAAA,IAAA,CAAK,mBAAA,EAAoB;AACzB,QAAA;AAAA,MAEF,KAAK,WAAA;AAEH,QAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AACnB,QAAA,IAAA,CAAK,mBAAA,EAAoB;AACzB,QAAA;AAAA;AACJ,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB,GAAA,EAAqB;AAC7C,IAAA,IAAI,CAAC,KAAK,UAAA,IAAc,CAAC,IAAI,MAAA,IAAU,CAAC,IAAI,KAAA,EAAO;AACjD,MAAA,IAAA,CAAK,IAAA,CAAK,IAAI,KAAA,CAAM,gCAAgC,CAAC,CAAA;AACrD,MAAA,IAAA,CAAK,mBAAA,EAAoB;AACzB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,MAAA,CAAO,gBAAA,CAAiB;AAAA,QAC7C,WAAW,EAAE,MAAA,EAAQ,IAAI,MAAA,EAAQ,KAAA,EAAO,IAAI,KAAA,EAAM;AAAA,QAClD,WAAW,IAAA,CAAK;AAAA,OACjB,CAAA;AAED,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,IAAA,CAAK,IAAA,CAAK,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AAClD,QAAA,IAAA,CAAK,mBAAA,EAAoB;AACzB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,MAAA,GAAyB,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAEnD,MAAA,IAAA,CAAK,IAAA,CAAK,WAAW,MAAM,CAAA;AAC3B,MAAA,IAAA,CAAK,YAAA,CAAa,WAAW,eAAe,CAAA;AAG5C,MAAA,IAAA,CAAK,WAAW,IAAA,CAAK;AAAA,QACnB,IAAA,EAAM,SAAA;AAAA,QACN,WAAW,IAAA,CAAK;AAAA,OACjB,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AACN,MAAA,IAAA,CAAK,IAAA,CAAK,IAAI,KAAA,CAAM,uCAAuC,CAAC,CAAA;AAC5D,MAAA,IAAA,CAAK,mBAAA,EAAoB;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,MAAA,EAAqB;AAChD,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,MAAM,KAAA;AAAA,QAChB,CAAA,EAAG,OAAO,CAAA,oBAAA,EAAuB,MAAA,CAAO,SAAS,CAAA;AAAA,OACnD;AAEA,MAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAE9C,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,MAAA,CAAO,gBAAA,CAAiB;AAAA,QAC7C,WAAW,EAAE,MAAA,EAAQ,KAAK,MAAA,EAAQ,KAAA,EAAO,KAAK,KAAA,EAAM;AAAA,QACpD,WAAW,MAAA,CAAO;AAAA,OACnB,CAAA;AAED,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,IAAA,CAAK,IAAA,CAAK,IAAI,KAAA,CAAM,sBAAsB,CAAC,CAAA;AAC3C,QAAA,IAAA,CAAK,mBAAA,EAAoB;AACzB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,MAAA,GAAyB,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AACnD,MAAA,IAAA,CAAK,IAAA,CAAK,WAAW,MAAM,CAAA;AAC3B,MAAA,gBAAA,EAAiB;AAAA,IACnB,CAAA,CAAA,MAAQ;AACN,MAAA,IAAA,CAAK,IAAA,CAAK,IAAI,KAAA,CAAM,yBAAyB,CAAC,CAAA;AAC9C,MAAA,IAAA,CAAK,mBAAA,EAAoB;AAAA,IAC3B;AAAA,EACF;AAAA,EAEQ,wBAAA,GAA2B;AACjC,IAAA,IAAA,CAAK,oBAAoB,YAAY;AACnC,MAAA,IAAI,QAAA,CAAS,oBAAoB,SAAA,EAAW;AAE5C,MAAA,MAAM,SAAS,cAAA,EAAe;AAC9B,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,MAAM,IAAA,CAAK,eAAe,MAAM,CAAA;AAAA,IAClC,CAAA;AAEA,IAAA,QAAA,CAAS,gBAAA,CAAiB,kBAAA,EAAoB,IAAA,CAAK,iBAAiB,CAAA;AAAA,EACtE;AAAA,EAEQ,wBAAA,GAA2B;AACjC,IAAA,IAAI,KAAK,iBAAA,EAAmB;AAC1B,MAAA,QAAA,CAAS,mBAAA,CAAoB,kBAAA,EAAoB,IAAA,CAAK,iBAAiB,CAAA;AACvE,MAAA,IAAA,CAAK,iBAAA,GAAoB,MAAA;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA,EAGQ,UAAU,OAAA,EAAkB;AAClC,IAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACrB,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,IAAA,CAAK,WAAW,IAAA,CAAK;AAAA,QACnB,IAAA,EAAM,mBAAA;AAAA,QACN,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,SAAA,EAAW,KAAK,MAAA,CAAO,SAAA;AAAA,QACvB,SAAA,EAAW,KAAK,MAAA,CAAO,SAAA;AAAA,QACvB,QAAA,EAAU,KAAK,MAAA,CAAO;AAAA,OACvB,CAAA;AAAA,IACH;AAEA,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AACtB,IAAA,gBAAA,EAAiB;AACjB,IAAA,IAAA,CAAK,YAAA,EAAa;AAElB,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,cAAA,EAAgB;AAC/B,MAAA,IAAA,CAAK,wBAAA,EAAyB;AAAA,IAChC;AACA,IAAA,IAAA,CAAK,YAAA,CAAa,UAAA,CAAW,OAAA,GAAU,WAAA,GAAc,MAAM,CAAA;AAAA,EAC7D;AAAA,EAEQ,mBAAA,GAAsB;AAC5B,IAAA,IAAA,CAAK,YAAA,CAAa,WAAW,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,cAAA,EAAgB;AACjC,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,IACb,GAAG,GAAG,CAAA;AAAA,EACR;AAAA,EAEQ,YAAA,GAAe;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,EAAA;AACjB,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAAA,EACpB;AAAA;AAAA,EAGA,OAAA,GAAU;AACR,IAAA,IAAA,CAAK,wBAAA,EAAyB;AAC9B,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AACtB,IAAA,IAAA,CAAK,YAAY,EAAC;AAAA,EACpB;AAAA,EAEQ,IAAA,CACN,OACA,OAAA,EACA;AACA,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,CAAG,OAAO,CAAC,CAAA;AAAA,EACpD;AAAA,EAEQ,KAAK,GAAA,EAAY;AACvB,IAAA,IAAA,CAAK,IAAA,CAAK,SAAS,GAAG,CAAA;AACtB,IAAA,IAAA,CAAK,YAAA,CAAa,WAAW,OAAO,CAAA;AAAA,EACtC;AACF","file":"index.js","sourcesContent":["import nacl from \"tweetnacl\";\nimport { decodeBase64, encodeBase64 } from \"tweetnacl-util\";\n\ninterface EncryptedMessage {\n cipher: string; // Base64 encoded\n nonce: string; // Base64 encoded\n}\n\nexport class CryptoService {\n generateSymmetricKey(): string {\n const key = nacl.randomBytes(32);\n return encodeBase64(key);\n }\n\n /**\n * Encrypt with symmetric key (secret-key encryption)\n * Use this when both parties share the same secret key\n * @param plaintext - Message to encrypt\n * @param keyString - Symmetric key (base64)\n * @returns Encrypted message with nonce\n */\n encryptSymmetric({\n plaintext,\n keyString,\n }: {\n plaintext: string;\n keyString: string;\n }): EncryptedMessage {\n const key = decodeBase64(keyString);\n const nonce = nacl.randomBytes(24);\n\n const messageBytes = new TextEncoder().encode(plaintext);\n\n const ciphertext = nacl.secretbox(messageBytes, nonce, key);\n\n return {\n cipher: encodeBase64(ciphertext),\n nonce: encodeBase64(nonce),\n };\n }\n\n /**\n * Decrypt with symmetric key\n * @param encrypted - Encrypted message with nonce\n * @param keyString - Symmetric key (base64)\n * @returns Decrypted plaintext\n */\n decryptSymmetric({\n encrypted,\n keyString,\n }: {\n encrypted: EncryptedMessage;\n keyString: string;\n }): string | null {\n try {\n const key = decodeBase64(keyString);\n const ciphertextBytes = decodeBase64(encrypted.cipher);\n const nonceBytes = decodeBase64(encrypted.nonce);\n\n const decrypted = nacl.secretbox.open(ciphertextBytes, nonceBytes, key);\n\n if (!decrypted) {\n throw new Error(\"Decryption failed - invalid key or corrupted data\");\n }\n\n const decoded = new TextDecoder().decode(decrypted);\n return decoded;\n } catch (error) {\n console.error(\"Decryption failed\", error);\n return null;\n }\n }\n}\n\nexport default CryptoService;\nexport type { EncryptedMessage };\n","// hybridStorage.ts\n/**\n * Hybrid storage: Try sessionStorage first, fallback to memory\n * Best of both worlds - survives page refresh but auto-cleans\n */\n\ninterface StorageOptions {\n ttlMs?: number;\n useSessionStorage?: boolean;\n}\n\nclass AuthStorage {\n private readonly prefix = \"pelican_auth_\";\n private readonly defaultTTL = 5 * 60 * 1000; // 5 minutes\n private memoryCache: Map<string, { value: any; expiresAt: number }> =\n new Map();\n\n /**\n * Store auth session with automatic cleanup\n */\n set(key: string, value: any, options: StorageOptions = {}): void {\n const { ttlMs = this.defaultTTL, useSessionStorage = true } = options;\n\n const expiresAt = Date.now() + ttlMs;\n const data = { value, expiresAt };\n\n // Try sessionStorage first\n if (useSessionStorage) {\n try {\n sessionStorage.setItem(`${this.prefix}${key}`, JSON.stringify(data));\n } catch (error) {\n console.warn(\"SessionStorage unavailable, using memory:\", error);\n this.setMemory(key, data);\n }\n } else {\n this.setMemory(key, data);\n }\n }\n\n /**\n * Get stored value if not expired\n */\n get(key: string): any | null {\n // Try sessionStorage first\n try {\n const stored = sessionStorage.getItem(`${this.prefix}${key}`);\n if (stored) {\n const data = JSON.parse(stored);\n\n // Check expiration\n if (Date.now() > data.expiresAt) {\n this.remove(key);\n return null;\n }\n\n return data.value;\n }\n } catch (error) {\n // Fallback to memory\n }\n\n // Try memory cache\n return this.getMemory(key);\n }\n\n /**\n * Remove specific key\n */\n remove(key: string): void {\n try {\n sessionStorage.removeItem(`${this.prefix}${key}`);\n } catch (error) {\n // Ignore\n }\n this.memoryCache.delete(key);\n }\n\n /**\n * Clear all auth data\n */\n clear(): void {\n // Clear sessionStorage\n try {\n Object.keys(sessionStorage).forEach((key) => {\n if (key.startsWith(this.prefix)) {\n sessionStorage.removeItem(key);\n }\n });\n } catch (error) {\n // Ignore\n }\n\n // Clear memory\n this.memoryCache.clear();\n }\n\n // ========================================\n // Memory cache helpers\n // ========================================\n\n private setMemory(\n key: string,\n data: { value: any; expiresAt: number }\n ): void {\n this.memoryCache.set(key, data);\n\n // Schedule cleanup\n const ttl = data.expiresAt - Date.now();\n setTimeout(() => {\n this.memoryCache.delete(key);\n }, ttl);\n }\n\n private getMemory(key: string): any | null {\n const data = this.memoryCache.get(key);\n if (!data) return null;\n\n if (Date.now() > data.expiresAt) {\n this.memoryCache.delete(key);\n return null;\n }\n\n return data.value;\n }\n}\n\n// Singleton instance\nconst storage = new AuthStorage();\n\n// ========================================\n// Convenience functions for auth flow\n// ========================================\n\nexport interface AuthSession {\n sessionId: string;\n sessionKey: string;\n}\n\nexport const storeAuthSession = (\n sessionId: string,\n sessionKey: string,\n ttlMs?: number\n): void => {\n storage.set(\"session\", { sessionId, sessionKey }, { ttlMs });\n};\n\nexport const getAuthSession = (): AuthSession | null => {\n return storage.get(\"session\");\n};\n\nexport const clearAuthSession = (): void => {\n storage.remove(\"session\");\n};\n\nexport const clearAllAuthData = (): void => {\n storage.clear();\n};\n\nexport default storage;\n","import type { PelicanAuthState } from \"../types/types\";\n\nexport class StateMachine {\n private state: PelicanAuthState = \"idle\";\n private listeners = new Set<(s: PelicanAuthState) => void>();\n\n get current() {\n return this.state;\n }\n\n transition(next: PelicanAuthState) {\n this.state = next;\n this.listeners.forEach((l) => l(next));\n }\n\n subscribe(fn: (s: PelicanAuthState) => void) {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n}\n","import { ISocketMessage } from \"../types/types\";\n\ntype TransportHandlers = {\n onOpen?: () => void;\n onMessage?: (msg: ISocketMessage) => void;\n onError?: (err: Event) => void;\n onClose?: (ev: CloseEvent) => void;\n};\n\nexport class Transport {\n private socket?: WebSocket;\n private handlers: TransportHandlers;\n private reconnectAttempts = 0;\n private maxReconnectAttempts = 5;\n private isExplicitlyClosed = false;\n private url?: string;\n\n constructor(handlers: TransportHandlers) {\n this.handlers = handlers;\n }\n\n connect(url: string) {\n this.url = url;\n this.isExplicitlyClosed = false;\n this.socket = new WebSocket(url);\n\n this.socket.onopen = () => {\n this.reconnectAttempts = 0;\n this.handlers.onOpen?.();\n };\n this.socket.onmessage = (e: MessageEvent) => {\n try {\n const data = JSON.parse(e.data);\n this.handlers.onMessage?.(data);\n } catch (err) {\n console.error(\"Failed to parse message\", err);\n }\n };\n this.socket.onerror = (e: Event) => {\n this.handlers.onError?.(e);\n };\n this.socket.onclose = (e: CloseEvent) => {\n this.handlers.onClose?.(e);\n\n // Only reconnect if the user didn't call .close() manually\n if (!this.isExplicitlyClosed) {\n this.attemptReconnect();\n }\n };\n }\n\n private attemptReconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n console.error(\"❌ Max reconnect attempts reached\");\n return;\n }\n\n this.reconnectAttempts++;\n\n const delay = Math.pow(2, this.reconnectAttempts) * 500;\n setTimeout(() => {\n if (this.url) this.connect(this.url);\n }, delay);\n }\n send(payload: ISocketMessage) {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(JSON.stringify(payload));\n }\n }\n\n close() {\n this.isExplicitlyClosed = true;\n this.socket?.close();\n }\n}\n","export const BASEURL = \"http://192.168.1.2:8080\";\n","import CryptoService from \"../utilities/crypto\";\nimport QRCode from \"qrcode\";\nimport {\n PelicanAuthConfig,\n PelicanAuthEventMap,\n ISocketMessage,\n IdentityResult,\n} from \"../types/types\";\nimport {\n storeAuthSession,\n getAuthSession,\n clearAuthSession,\n AuthSession,\n} from \"../utilities/storage\";\nimport { StateMachine } from \"../utilities/stateMachine\";\nimport { Transport } from \"../utilities/transport\";\n\nimport { BASEURL } from \"../constants\";\n\ntype Listener<T> = (payload: T) => void;\n\n/**\n * PelicanAuth SDK\n * Handles cross-device authentication via WebSockets and E2EE (End-to-End Encryption).\n */\nexport class PelicanAuthentication {\n private readonly crypto = new CryptoService();\n private readonly stateMachine = new StateMachine();\n\n private transport?: Transport;\n private sessionId = \"\";\n private sessionKey: string | null = null;\n\n private visibilityHandler?: () => void;\n\n private listeners: {\n [K in keyof PelicanAuthEventMap]?: Set<Listener<any>>;\n } = {};\n\n private readonly config: Required<PelicanAuthConfig>;\n\n constructor(config: PelicanAuthConfig) {\n if (!config.publicKey) throw new Error(\"Missing publicKey\");\n if (!config.projectId) throw new Error(\"Missing projectId\");\n if (!config.authType) throw new Error(\"Missing authType\");\n\n this.config = {\n continuousMode: false,\n forceQRCode: false,\n ...config,\n };\n\n // Sync internal state machine changes with the 'state' event\n this.stateMachine.subscribe((s) => this.emit(\"state\", s));\n this.attachVisibilityRecovery();\n }\n\n /* -------------------- public API -------------------- */\n\n /**\n * Subscribe to SDK events (qr, deeplink, success, error, state)\n * @returns Unsubscribe function\n */\n on<K extends keyof PelicanAuthEventMap>(\n event: K,\n cb: Listener<PelicanAuthEventMap[K]>\n ) {\n this.listeners[event] ??= new Set();\n this.listeners[event]!.add(cb);\n return () => this.listeners[event]!.delete(cb);\n }\n\n /**\n * Initializes the authentication flow.\n * Fetches a relay, establishes a WebSocket, and generates the E2EE session key.\n */\n async start() {\n if (this.stateMachine.current !== \"idle\") return;\n\n this.resetSession();\n this.stateMachine.transition(\"initializing\");\n\n try {\n const relay = await this.fetchRelayUrl();\n\n // Generate a fresh symmetric key for this specific session\n this.sessionKey = this.crypto.generateSymmetricKey();\n this.sessionId = crypto.randomUUID() + crypto.randomUUID();\n\n this.transport = new Transport({\n onOpen: () => {\n // Register this browser session with the relay\n this.transport!.send({\n type: \"register\",\n sessionID: this.sessionId,\n ...this.config,\n });\n this.stateMachine.transition(\"awaiting-pair\");\n },\n onMessage: (msg: ISocketMessage) => this.handleMessage(msg),\n onError: () => this.fail(new Error(\"WebSocket connection failed\")),\n });\n\n this.transport.connect(relay);\n await this.emitEntryPoint();\n } catch (err) {\n this.fail(err instanceof Error ? err : new Error(\"Start failed\"));\n }\n }\n\n /**\n * Manually stops the authentication process and closes connections.\n */\n stop() {\n this.terminate(false);\n }\n\n /* -------------------- internals -------------------- */\n\n /** Fetches the WebSocket Relay URL from the backend */\n private async fetchRelayUrl(): Promise<string> {\n const { publicKey, projectId, authType } = this.config;\n const res = await fetch(\n `${BASEURL}/relay?public_key=${publicKey}&auth_type=${authType}&project_id=${projectId}`\n );\n\n if (!res.ok) {\n throw new Error(\"Failed to fetch relay URL\");\n }\n\n const json = await res.json();\n return json.relay_url;\n }\n\n /**\n * Decides whether to show a QR code (Desktop) or a Deep Link (Mobile).\n */\n private async emitEntryPoint() {\n const payload = {\n sessionID: this.sessionId,\n sessionKey: this.sessionKey,\n publicKey: this.config.publicKey,\n authType: this.config.authType,\n projectId: this.config.projectId,\n url: window.location.href,\n };\n\n const shouldUseQR =\n this.config.forceQRCode ||\n !/Android|iPhone|iPad/i.test(navigator.userAgent);\n\n if (!shouldUseQR && this.sessionKey) {\n // For mobile: Store session locally so we can recover when the user returns from the app\n storeAuthSession(this.sessionId, this.sessionKey, 5 * 60_000);\n this.emit(\n \"deeplink\",\n `pelicanvault://auth/deep-link?sessionID=${encodeURIComponent(\n this.sessionId\n )}&sessionKey=${encodeURIComponent(\n this.sessionKey\n )}&publicKey=${encodeURIComponent(this.config.publicKey)}&authType=${\n this.config.authType\n }&projectId=${encodeURIComponent(\n this.config.projectId\n )}&url=${encodeURIComponent(window.location.href)}`\n );\n } else {\n const qr = await QRCode.toDataURL(JSON.stringify(payload), {\n type: \"image/png\",\n scale: 3,\n color: { light: \"#FFFFF5\", dark: \"#121212\" },\n });\n this.emit(\"qr\", qr);\n }\n }\n\n /** Main WebSocket message router */\n private handleMessage(msg: ISocketMessage) {\n switch (msg.type) {\n case \"paired\":\n this.stateMachine.transition(\"paired\");\n this.transport!.send({\n type: \"authenticate\",\n sessionID: this.sessionId,\n ...this.config,\n url: window.location.href,\n });\n return;\n\n case \"phone-auth-success\":\n // Mobile device successfully authenticated and sent encrypted credentials\n this.handleAuthSuccess(msg);\n return;\n\n case \"phone-terminated\":\n this.fail(new Error(\"Authenticating device terminated the connection\"));\n this.restartIfContinuous();\n return;\n\n case \"confirmed\":\n // Handshake complete\n this.terminate(true);\n this.restartIfContinuous();\n return;\n }\n }\n\n /**\n * Decrypts the identity payload received from the phone using the session key.\n */\n private handleAuthSuccess(msg: ISocketMessage) {\n if (!this.sessionKey || !msg.cipher || !msg.nonce) {\n this.fail(new Error(\"Invalid authentication payload\"));\n this.restartIfContinuous();\n return;\n }\n\n try {\n const decrypted = this.crypto.decryptSymmetric({\n encrypted: { cipher: msg.cipher, nonce: msg.nonce },\n keyString: this.sessionKey,\n });\n\n if (!decrypted) {\n this.fail(new Error(\"Invalid authentication data\"));\n this.restartIfContinuous();\n return;\n }\n\n const result: IdentityResult = JSON.parse(decrypted);\n\n this.emit(\"success\", result);\n this.stateMachine.transition(\"authenticated\");\n\n // Signal to the relay/phone that we have received and decrypted the data\n this.transport?.send({\n type: \"confirm\",\n sessionID: this.sessionId,\n });\n } catch {\n this.fail(new Error(\"Failed to decrypt authentication data\"));\n this.restartIfContinuous();\n }\n }\n\n /**\n * Logic to handle users returning to the browser tab after using the mobile app.\n * Checks the server for a completed session that might have finished while in background.\n */\n\n private async getCachedEntry(cached: AuthSession) {\n try {\n const res = await fetch(\n `${BASEURL}/session?session_id=${cached.sessionId}`\n );\n\n if (!res.ok) throw new Error(\"Invalid session\");\n\n const data = await res.json();\n const decrypted = this.crypto.decryptSymmetric({\n encrypted: { cipher: data.cipher, nonce: data.nonce },\n keyString: cached.sessionKey,\n });\n\n if (!decrypted) {\n this.fail(new Error(\"Invalid session data\"));\n this.restartIfContinuous();\n return;\n }\n const result: IdentityResult = JSON.parse(decrypted);\n this.emit(\"success\", result);\n clearAuthSession();\n } catch {\n this.fail(new Error(\"Session recovery failed\"));\n this.restartIfContinuous();\n }\n }\n\n private attachVisibilityRecovery() {\n this.visibilityHandler = async () => {\n if (document.visibilityState !== \"visible\") return;\n\n const cached = getAuthSession();\n if (!cached) return;\n await this.getCachedEntry(cached);\n };\n\n document.addEventListener(\"visibilitychange\", this.visibilityHandler);\n }\n\n private detachVisibilityRecovery() {\n if (this.visibilityHandler) {\n document.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n this.visibilityHandler = undefined;\n }\n }\n\n /** Cleans up the current session state */\n private terminate(success: boolean) {\n if (!this.transport) return;\n if (!success) {\n this.transport?.send({\n type: \"client-terminated\",\n sessionID: this.sessionId,\n projectId: this.config.projectId,\n publicKey: this.config.publicKey,\n authType: this.config.authType,\n });\n }\n\n this.transport?.close();\n clearAuthSession();\n this.resetSession();\n\n if (!this.config.continuousMode) {\n this.detachVisibilityRecovery();\n }\n this.stateMachine.transition(success ? \"confirmed\" : \"idle\");\n }\n\n private restartIfContinuous() {\n this.stateMachine.transition(\"idle\");\n if (!this.config.continuousMode) return;\n setTimeout(() => {\n this.start();\n }, 150);\n }\n\n private resetSession() {\n this.sessionId = \"\";\n this.sessionKey = null;\n }\n\n /** Completely destroys the instance and removes all listeners */\n destroy() {\n this.detachVisibilityRecovery();\n this.transport?.close();\n this.listeners = {};\n }\n\n private emit<K extends keyof PelicanAuthEventMap>(\n event: K,\n payload: PelicanAuthEventMap[K]\n ) {\n this.listeners[event]?.forEach((cb) => cb(payload));\n }\n\n private fail(err: Error) {\n this.emit(\"error\", err);\n this.stateMachine.transition(\"error\");\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,eAAe,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.mjs CHANGED
@@ -234,7 +234,7 @@ var Transport = class {
234
234
  };
235
235
 
236
236
  // src/constants.ts
237
- var BASEURL = "http://192.168.1.2:8080";
237
+ var BASEURL = "https://identityapi.pelicanidentity.com";
238
238
 
239
239
  // src/engine/engine.ts
240
240
  var PelicanAuthentication = class {
@@ -273,6 +273,7 @@ var PelicanAuthentication = class {
273
273
  async start() {
274
274
  if (this.stateMachine.current !== "idle") return;
275
275
  this.resetSession();
276
+ clearAuthSession();
276
277
  this.stateMachine.transition("initializing");
277
278
  try {
278
279
  const relay = await this.fetchRelayUrl();
@@ -310,7 +311,8 @@ var PelicanAuthentication = class {
310
311
  `${BASEURL}/relay?public_key=${publicKey}&auth_type=${authType}&project_id=${projectId}`
311
312
  );
312
313
  if (!res.ok) {
313
- throw new Error("Failed to fetch relay URL");
314
+ const error = await res.text();
315
+ throw new Error(error);
314
316
  }
315
317
  const json = await res.json();
316
318
  return json.relay_url;
@@ -344,7 +346,7 @@ var PelicanAuthentication = class {
344
346
  const qr = await QRCode.toDataURL(JSON.stringify(payload), {
345
347
  type: "image/png",
346
348
  scale: 3,
347
- color: { light: "#FFFFF5", dark: "#121212" }
349
+ color: { light: "#ffffff", dark: "#424242ff" }
348
350
  });
349
351
  this.emit("qr", qr);
350
352
  }
@@ -450,7 +452,6 @@ var PelicanAuthentication = class {
450
452
  }
451
453
  /** Cleans up the current session state */
452
454
  terminate(success) {
453
- if (!this.transport) return;
454
455
  if (!success) {
455
456
  this.transport?.send({
456
457
  type: "client-terminated",
@@ -470,6 +471,7 @@ var PelicanAuthentication = class {
470
471
  }
471
472
  restartIfContinuous() {
472
473
  this.stateMachine.transition("idle");
474
+ this.transport?.close();
473
475
  if (!this.config.continuousMode) return;
474
476
  setTimeout(() => {
475
477
  this.start();