@arcblock/did-connect-core 4.0.1 → 4.0.3
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.cjs +1378 -0
- package/package.json +4 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ConnectError: () => ConnectError,
|
|
24
|
+
DEFAULTS: () => DEFAULTS,
|
|
25
|
+
DID_PREFIX: () => DID_PREFIX,
|
|
26
|
+
ERROR_CODES: () => ERROR_CODES,
|
|
27
|
+
EmailFlow: () => EmailFlow,
|
|
28
|
+
FetchHttpAdapter: () => FetchHttpAdapter,
|
|
29
|
+
OAuthFlow: () => OAuthFlow,
|
|
30
|
+
PasskeyFlow: () => PasskeyFlow,
|
|
31
|
+
STORAGE_KEYS: () => STORAGE_KEYS,
|
|
32
|
+
SessionManager: () => SessionManager,
|
|
33
|
+
TERMINAL_STATUSES: () => TERMINAL_STATUSES,
|
|
34
|
+
TOKEN_STATUS: () => TOKEN_STATUS,
|
|
35
|
+
TokenSession: () => TokenSession,
|
|
36
|
+
VERSION: () => VERSION,
|
|
37
|
+
buildActionPrefix: () => buildActionPrefix,
|
|
38
|
+
decodeConnectUrl: () => decodeConnectUrl,
|
|
39
|
+
encodeConnectUrl: () => encodeConnectUrl,
|
|
40
|
+
getApiErrorMessage: () => getApiErrorMessage,
|
|
41
|
+
getBrowserLocale: () => getBrowserLocale,
|
|
42
|
+
getConnectedInfo: () => getConnectedInfo,
|
|
43
|
+
getDidChainLabel: () => getDidChainLabel,
|
|
44
|
+
getDidDisplayParts: () => getDidDisplayParts,
|
|
45
|
+
getWebAuthnErrorMessage: () => getWebAuthnErrorMessage,
|
|
46
|
+
isEthereumDid: () => isEthereumDid,
|
|
47
|
+
openPopup: () => openPopup,
|
|
48
|
+
parseNextWorkflow: () => parseNextWorkflow,
|
|
49
|
+
parseTokenFromUrl: () => parseTokenFromUrl,
|
|
50
|
+
stringifyParams: () => stringifyParams,
|
|
51
|
+
truncateDid: () => truncateDid,
|
|
52
|
+
waitForPopupMessage: () => waitForPopupMessage
|
|
53
|
+
});
|
|
54
|
+
module.exports = __toCommonJS(index_exports);
|
|
55
|
+
|
|
56
|
+
// src/constants.ts
|
|
57
|
+
var TOKEN_STATUS = {
|
|
58
|
+
CREATED: "created",
|
|
59
|
+
SCANNED: "scanned",
|
|
60
|
+
SUCCEED: "succeed",
|
|
61
|
+
ERROR: "error",
|
|
62
|
+
TIMEOUT: "timeout",
|
|
63
|
+
BUSY: "busy",
|
|
64
|
+
FORBIDDEN: "forbidden"
|
|
65
|
+
};
|
|
66
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeed", "error", "timeout"]);
|
|
67
|
+
var DEFAULTS = {
|
|
68
|
+
API_PREFIX: "/api/did",
|
|
69
|
+
SERVICE_PREFIX: "/.well-known/service",
|
|
70
|
+
ACTION: "login",
|
|
71
|
+
TOKEN_TIMEOUT: 3e5,
|
|
72
|
+
CHECK_INTERVAL: 2e3,
|
|
73
|
+
TOKEN_KEY: "_t_",
|
|
74
|
+
TOKEN_DELIVERY: "cookie",
|
|
75
|
+
CREATE_THROTTLE: 500,
|
|
76
|
+
AUTO_CHECK_INTERVAL: 6e4
|
|
77
|
+
};
|
|
78
|
+
var STORAGE_KEYS = {
|
|
79
|
+
SESSION_TOKEN: "login_token",
|
|
80
|
+
REFRESH_TOKEN: "refresh_token",
|
|
81
|
+
ENC_KEY: "__encKey",
|
|
82
|
+
DEC_KEY: "__decKey",
|
|
83
|
+
CSRF_TOKEN: "x-csrf-token",
|
|
84
|
+
CONNECTED_DID: "connected_did",
|
|
85
|
+
CONNECTED_PK: "connected_pk",
|
|
86
|
+
CONNECTED_APP: "connected_app",
|
|
87
|
+
CONNECTED_WALLET_OS: "connected_wallet_os"
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/did-display.ts
|
|
91
|
+
var DID_PREFIX = "did:abt:";
|
|
92
|
+
function isEthereumDid(did) {
|
|
93
|
+
if (!did || typeof did !== "string") return false;
|
|
94
|
+
const address = did.replace(DID_PREFIX, "");
|
|
95
|
+
return /^(0x)?[0-9a-f]{40}$/i.test(address);
|
|
96
|
+
}
|
|
97
|
+
function truncateDid(did, startChars = 8, endChars = 6) {
|
|
98
|
+
if (!did || typeof did !== "string") return "";
|
|
99
|
+
if (did.length <= startChars + endChars + 3) return did;
|
|
100
|
+
return `${did.slice(0, startChars)}...${did.slice(-endChars)}`;
|
|
101
|
+
}
|
|
102
|
+
function getDidChainLabel(did) {
|
|
103
|
+
return isEthereumDid(did) ? "ETH" : "ABT";
|
|
104
|
+
}
|
|
105
|
+
function getDidDisplayParts(did, options = {}) {
|
|
106
|
+
if (!did || typeof did !== "string") {
|
|
107
|
+
return { prefix: "DID: ABT:", chainLabel: "ABT", address: "", compact: "", copyText: "" };
|
|
108
|
+
}
|
|
109
|
+
const { startChars = 8, endChars = 6 } = options;
|
|
110
|
+
const isEth = isEthereumDid(did);
|
|
111
|
+
const address = did.replace(DID_PREFIX, "");
|
|
112
|
+
const chainLabel = isEth ? "ETH" : "ABT";
|
|
113
|
+
const includePrefix = options.includePrefix ?? !isEth;
|
|
114
|
+
return {
|
|
115
|
+
prefix: `DID: ${chainLabel}:`,
|
|
116
|
+
chainLabel,
|
|
117
|
+
address,
|
|
118
|
+
compact: truncateDid(address, startChars, endChars),
|
|
119
|
+
copyText: includePrefix ? `${DID_PREFIX}${address}` : address
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/errors.ts
|
|
124
|
+
var ERROR_CODES = {
|
|
125
|
+
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
126
|
+
TOKEN_NOT_FOUND: "TOKEN_NOT_FOUND",
|
|
127
|
+
TOKEN_TIMEOUT: "TOKEN_TIMEOUT",
|
|
128
|
+
ALREADY_EXIST: "ALREADY_EXIST",
|
|
129
|
+
ALREADY_BIND: "ALREADY_BIND",
|
|
130
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
131
|
+
INVALID_RESPONSE: "INVALID_RESPONSE",
|
|
132
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
133
|
+
FORBIDDEN: "FORBIDDEN"
|
|
134
|
+
};
|
|
135
|
+
var ConnectError = class extends Error {
|
|
136
|
+
code;
|
|
137
|
+
constructor(code, message) {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "ConnectError";
|
|
140
|
+
this.code = code;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/email-flow.ts
|
|
145
|
+
var EmailFlow = class {
|
|
146
|
+
_servicePrefix;
|
|
147
|
+
_http;
|
|
148
|
+
constructor(config, http) {
|
|
149
|
+
this._servicePrefix = config.servicePrefix ?? DEFAULTS.SERVICE_PREFIX;
|
|
150
|
+
this._http = http;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Send a verification code to the given email address.
|
|
154
|
+
* POST {servicePrefix}/api/email/sendCode
|
|
155
|
+
* Returns { id: string } — the code ID for subsequent checkStatus.
|
|
156
|
+
*/
|
|
157
|
+
async sendCode(email, locale) {
|
|
158
|
+
if (email == null) {
|
|
159
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, "Email is required");
|
|
160
|
+
}
|
|
161
|
+
const url = `${this._servicePrefix}/api/email/sendCode`;
|
|
162
|
+
return this._http.post(url, { email, locale });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check verification code status.
|
|
166
|
+
* GET {servicePrefix}/api/email/status?codeId=...
|
|
167
|
+
*/
|
|
168
|
+
async checkStatus(codeId) {
|
|
169
|
+
if (!codeId) {
|
|
170
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, "codeId is required");
|
|
171
|
+
}
|
|
172
|
+
const url = `${this._servicePrefix}/api/email/status`;
|
|
173
|
+
return this._http.get(url, {
|
|
174
|
+
params: { codeId }
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Login with verification code or magic token.
|
|
179
|
+
* POST {servicePrefix}/api/email/login
|
|
180
|
+
* Returns AuthResult (same shape as OAuth login).
|
|
181
|
+
*/
|
|
182
|
+
async login(params) {
|
|
183
|
+
const url = `${this._servicePrefix}/api/email/login`;
|
|
184
|
+
return this._http.post(url, params);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/fetch-adapter.ts
|
|
189
|
+
var FetchHttpAdapter = class {
|
|
190
|
+
_defaultHeaders;
|
|
191
|
+
_onRequest;
|
|
192
|
+
constructor(options) {
|
|
193
|
+
this._defaultHeaders = options?.headers ?? {};
|
|
194
|
+
this._onRequest = options?.onRequest;
|
|
195
|
+
}
|
|
196
|
+
async get(url, options) {
|
|
197
|
+
this._validateUrl(url);
|
|
198
|
+
const queryString = options?.params ? this._serializeParams(options.params) : "";
|
|
199
|
+
const fullUrl = queryString ? `${url}${url.includes("?") ? "&" : "?"}${queryString}` : url;
|
|
200
|
+
const headers = {
|
|
201
|
+
...this._defaultHeaders,
|
|
202
|
+
...options?.headers
|
|
203
|
+
};
|
|
204
|
+
let init = {
|
|
205
|
+
method: "GET",
|
|
206
|
+
headers,
|
|
207
|
+
signal: options?.signal
|
|
208
|
+
};
|
|
209
|
+
if (this._onRequest) {
|
|
210
|
+
init = this._onRequest(fullUrl, init);
|
|
211
|
+
}
|
|
212
|
+
return this._execute(fullUrl, init);
|
|
213
|
+
}
|
|
214
|
+
async post(url, body, options) {
|
|
215
|
+
this._validateUrl(url);
|
|
216
|
+
const queryString = options?.params ? this._serializeParams(options.params) : "";
|
|
217
|
+
const fullUrl = queryString ? `${url}${url.includes("?") ? "&" : "?"}${queryString}` : url;
|
|
218
|
+
const headers = {
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
...this._defaultHeaders,
|
|
221
|
+
...options?.headers
|
|
222
|
+
};
|
|
223
|
+
let init = {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers,
|
|
226
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
227
|
+
signal: options?.signal
|
|
228
|
+
};
|
|
229
|
+
if (this._onRequest) {
|
|
230
|
+
init = this._onRequest(fullUrl, init);
|
|
231
|
+
}
|
|
232
|
+
return this._execute(fullUrl, init);
|
|
233
|
+
}
|
|
234
|
+
_validateUrl(url) {
|
|
235
|
+
if (!url?.trim()) {
|
|
236
|
+
throw new ConnectError(ERROR_CODES.NETWORK_ERROR, "Request URL must not be empty");
|
|
237
|
+
}
|
|
238
|
+
if (url.trim().toLowerCase().startsWith("javascript:")) {
|
|
239
|
+
throw new ConnectError(ERROR_CODES.NETWORK_ERROR, "Invalid URL protocol");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async _execute(url, init) {
|
|
243
|
+
let response;
|
|
244
|
+
try {
|
|
245
|
+
response = await globalThis.fetch(url, init);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (err?.name === "AbortError") {
|
|
248
|
+
throw err;
|
|
249
|
+
}
|
|
250
|
+
throw new ConnectError(ERROR_CODES.NETWORK_ERROR, `Network request failed: ${err?.message ?? "Unknown error"}`);
|
|
251
|
+
}
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
let errorMessage = "";
|
|
254
|
+
let errorCode = "";
|
|
255
|
+
try {
|
|
256
|
+
const body = await response.text();
|
|
257
|
+
try {
|
|
258
|
+
const json = JSON.parse(body);
|
|
259
|
+
if (json.error) errorMessage = json.error;
|
|
260
|
+
if (json.code) errorCode = json.code;
|
|
261
|
+
} catch {
|
|
262
|
+
errorMessage = body;
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
const code = errorCode === "FORBIDDEN" ? ERROR_CODES.FORBIDDEN : ERROR_CODES.NETWORK_ERROR;
|
|
267
|
+
throw new ConnectError(
|
|
268
|
+
code,
|
|
269
|
+
errorMessage || `HTTP ${response.status} ${response.statusText || "Error"}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (response.status === 204) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
const text = await response.text();
|
|
276
|
+
if (!text.trim()) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
280
|
+
if (!contentType.includes("application/json") && !contentType.includes("+json")) {
|
|
281
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, `Expected JSON response but received: ${contentType || "unknown content type"}`);
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
return JSON.parse(text);
|
|
285
|
+
} catch {
|
|
286
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, "Failed to parse JSON response");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
_serializeParams(params) {
|
|
290
|
+
const parts = [];
|
|
291
|
+
for (const key of Object.keys(params)) {
|
|
292
|
+
const value = params[key];
|
|
293
|
+
if (value === null || value === void 0) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (Array.isArray(value)) {
|
|
297
|
+
for (const item of value) {
|
|
298
|
+
if (item === null || item === void 0) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`);
|
|
302
|
+
}
|
|
303
|
+
} else if (typeof value === "boolean") {
|
|
304
|
+
parts.push(`${encodeURIComponent(key)}=${value ? "true" : "false"}`);
|
|
305
|
+
} else {
|
|
306
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return parts.join("&");
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/oauth-flow.ts
|
|
314
|
+
var OAuthFlow = class {
|
|
315
|
+
_servicePrefix;
|
|
316
|
+
_http;
|
|
317
|
+
constructor(config, http) {
|
|
318
|
+
this._servicePrefix = config.servicePrefix ?? DEFAULTS.SERVICE_PREFIX;
|
|
319
|
+
this._http = http;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Fetch enabled OAuth providers.
|
|
323
|
+
* GET {servicePrefix}/api/oauth/configs
|
|
324
|
+
* Response: { providers: OAuthProvider[] }
|
|
325
|
+
*/
|
|
326
|
+
async getProviders() {
|
|
327
|
+
const url = `${this._servicePrefix}/api/oauth/configs`;
|
|
328
|
+
const res = await this._http.get(url);
|
|
329
|
+
if (!res || !Array.isArray(res.providers)) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
return res.providers;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Exchange an OAuth authorization code for a session.
|
|
336
|
+
* POST {servicePrefix}/api/oauth/login
|
|
337
|
+
*/
|
|
338
|
+
async exchangeCode(provider, code, params) {
|
|
339
|
+
if (code == null) {
|
|
340
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, "OAuth code is required");
|
|
341
|
+
}
|
|
342
|
+
const url = `${this._servicePrefix}/api/oauth/login`;
|
|
343
|
+
return this._http.post(url, { provider, code, ...params });
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Bind an OAuth account to the current user.
|
|
347
|
+
* POST {servicePrefix}/api/oauth/bind
|
|
348
|
+
*/
|
|
349
|
+
async bind(provider, code, params) {
|
|
350
|
+
const url = `${this._servicePrefix}/api/oauth/bind`;
|
|
351
|
+
return this._http.post(url, { provider, code, ...params });
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Unbind an OAuth account.
|
|
355
|
+
* POST {servicePrefix}/api/oauth/unbind
|
|
356
|
+
*/
|
|
357
|
+
async unbind(provider, accountDid) {
|
|
358
|
+
const url = `${this._servicePrefix}/api/oauth/unbind`;
|
|
359
|
+
return this._http.post(url, { provider, accountDid });
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Fetch available OAuth passports (switchable accounts).
|
|
363
|
+
* GET {servicePrefix}/api/oauth/passports
|
|
364
|
+
*/
|
|
365
|
+
async getPassports() {
|
|
366
|
+
const url = `${this._servicePrefix}/api/oauth/passports`;
|
|
367
|
+
const res = await this._http.get(url);
|
|
368
|
+
return Array.isArray(res) ? res : [];
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/passkey-flow.ts
|
|
373
|
+
var PasskeyFlow = class {
|
|
374
|
+
_servicePrefix;
|
|
375
|
+
_http;
|
|
376
|
+
constructor(config, http) {
|
|
377
|
+
this._servicePrefix = config.servicePrefix ?? DEFAULTS.SERVICE_PREFIX;
|
|
378
|
+
this._http = http;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get WebAuthn registration options (challenge, etc.).
|
|
382
|
+
* GET {servicePrefix}/api/passkey/register
|
|
383
|
+
*/
|
|
384
|
+
async getRegisterOptions(params) {
|
|
385
|
+
const url = `${this._servicePrefix}/api/passkey/register`;
|
|
386
|
+
return this._http.get(url, { params });
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Submit a registration credential.
|
|
390
|
+
* POST {servicePrefix}/api/passkey/register?challenge=...
|
|
391
|
+
*/
|
|
392
|
+
async submitRegistration(credential, params) {
|
|
393
|
+
if (credential == null) {
|
|
394
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, "Credential is required");
|
|
395
|
+
}
|
|
396
|
+
const url = `${this._servicePrefix}/api/passkey/register`;
|
|
397
|
+
return this._http.post(url, credential, { params });
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get WebAuthn authentication options (challenge, etc.).
|
|
401
|
+
* GET {servicePrefix}/api/passkey/auth
|
|
402
|
+
*/
|
|
403
|
+
async getAuthOptions(params) {
|
|
404
|
+
const url = `${this._servicePrefix}/api/passkey/auth`;
|
|
405
|
+
return this._http.get(url, { params });
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Submit an authentication credential.
|
|
409
|
+
* POST {servicePrefix}/api/passkey/auth?challenge=...&targetAppPid=...
|
|
410
|
+
*/
|
|
411
|
+
async submitAuthentication(credential, params) {
|
|
412
|
+
if (credential == null) {
|
|
413
|
+
throw new ConnectError(ERROR_CODES.INVALID_RESPONSE, "Credential is required");
|
|
414
|
+
}
|
|
415
|
+
const url = `${this._servicePrefix}/api/passkey/auth`;
|
|
416
|
+
return this._http.post(url, credential, { params });
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Fetch passkey passport list.
|
|
420
|
+
* GET {servicePrefix}/api/passkey/passports
|
|
421
|
+
*/
|
|
422
|
+
async getPassports() {
|
|
423
|
+
const url = `${this._servicePrefix}/api/passkey/passports`;
|
|
424
|
+
const res = await this._http.get(url);
|
|
425
|
+
return Array.isArray(res) ? res : [];
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/session-manager.ts
|
|
430
|
+
var SessionManager = class {
|
|
431
|
+
// -- state ----------------------------------------------------------------
|
|
432
|
+
_state = {
|
|
433
|
+
user: null,
|
|
434
|
+
loading: false,
|
|
435
|
+
error: null,
|
|
436
|
+
initialized: false
|
|
437
|
+
};
|
|
438
|
+
// -- deps -----------------------------------------------------------------
|
|
439
|
+
_config;
|
|
440
|
+
_http;
|
|
441
|
+
_storage;
|
|
442
|
+
// -- token keys -----------------------------------------------------------
|
|
443
|
+
_tokenKey;
|
|
444
|
+
_refreshTokenKey;
|
|
445
|
+
// -- auto-check -----------------------------------------------------------
|
|
446
|
+
_autoCheckTimer = null;
|
|
447
|
+
_autoCheckInterval;
|
|
448
|
+
// -- events ---------------------------------------------------------------
|
|
449
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
450
|
+
// -- concurrency ----------------------------------------------------------
|
|
451
|
+
_refreshing = null;
|
|
452
|
+
// -- lifecycle ------------------------------------------------------------
|
|
453
|
+
_destroyed = false;
|
|
454
|
+
// =========================================================================
|
|
455
|
+
// Constructor
|
|
456
|
+
// =========================================================================
|
|
457
|
+
constructor(config, http, storage, options) {
|
|
458
|
+
this._config = {
|
|
459
|
+
...config,
|
|
460
|
+
apiPrefix: config.apiPrefix ?? DEFAULTS.API_PREFIX,
|
|
461
|
+
action: config.action ?? DEFAULTS.ACTION,
|
|
462
|
+
servicePrefix: config.servicePrefix ?? DEFAULTS.SERVICE_PREFIX,
|
|
463
|
+
tokenTimeout: config.tokenTimeout ?? DEFAULTS.TOKEN_TIMEOUT,
|
|
464
|
+
checkInterval: config.checkInterval ?? DEFAULTS.CHECK_INTERVAL,
|
|
465
|
+
tokenDelivery: config.tokenDelivery ?? DEFAULTS.TOKEN_DELIVERY
|
|
466
|
+
};
|
|
467
|
+
this._http = http;
|
|
468
|
+
this._storage = storage;
|
|
469
|
+
this._tokenKey = options?.tokenKey ?? STORAGE_KEYS.SESSION_TOKEN;
|
|
470
|
+
this._refreshTokenKey = options?.refreshTokenKey ?? STORAGE_KEYS.REFRESH_TOKEN;
|
|
471
|
+
this._autoCheckInterval = options?.autoCheckInterval ?? DEFAULTS.AUTO_CHECK_INTERVAL;
|
|
472
|
+
}
|
|
473
|
+
// =========================================================================
|
|
474
|
+
// Public getters
|
|
475
|
+
// =========================================================================
|
|
476
|
+
get state() {
|
|
477
|
+
return this._state;
|
|
478
|
+
}
|
|
479
|
+
// =========================================================================
|
|
480
|
+
// Public methods
|
|
481
|
+
// =========================================================================
|
|
482
|
+
/**
|
|
483
|
+
* Fetch the current session from the server.
|
|
484
|
+
*
|
|
485
|
+
* The server may rotate tokens transparently — if `nextToken` /
|
|
486
|
+
* `nextRefreshToken` are present in the response they are persisted and
|
|
487
|
+
* a `tokenRefreshed` event is emitted.
|
|
488
|
+
*/
|
|
489
|
+
async getSession() {
|
|
490
|
+
const url = `${this._config.apiPrefix}/session`;
|
|
491
|
+
const token = this._storage.get(this._tokenKey);
|
|
492
|
+
const headers = {};
|
|
493
|
+
if (token) {
|
|
494
|
+
headers.Authorization = `Bearer ${token}`;
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
const res = await this._http.get(url, { headers });
|
|
498
|
+
if (res.nextToken) {
|
|
499
|
+
this._storage.set(this._tokenKey, res.nextToken);
|
|
500
|
+
}
|
|
501
|
+
if (res.nextRefreshToken) {
|
|
502
|
+
this._storage.set(this._refreshTokenKey, res.nextRefreshToken);
|
|
503
|
+
}
|
|
504
|
+
if (res.nextToken || res.nextRefreshToken) {
|
|
505
|
+
this._emit("tokenRefreshed");
|
|
506
|
+
}
|
|
507
|
+
const user = res.user ?? null;
|
|
508
|
+
this._setState({ user, error: null });
|
|
509
|
+
this._emit("userChange", user);
|
|
510
|
+
return user;
|
|
511
|
+
} catch (err) {
|
|
512
|
+
if (_isUnauthorized(err)) {
|
|
513
|
+
try {
|
|
514
|
+
await this.refreshSession();
|
|
515
|
+
return await this._getSessionDirect();
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const message = _errorMessage(err);
|
|
520
|
+
this._setState({ error: message });
|
|
521
|
+
this._emit("error", new ConnectError(ERROR_CODES.NETWORK_ERROR, message));
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Refresh the session token using the stored refresh token.
|
|
527
|
+
*
|
|
528
|
+
* Concurrent calls are de-duplicated: only the first caller performs the
|
|
529
|
+
* network request; subsequent callers await the same promise.
|
|
530
|
+
*/
|
|
531
|
+
async refreshSession() {
|
|
532
|
+
if (this._refreshing !== null) {
|
|
533
|
+
await this._refreshing;
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
let resolve;
|
|
537
|
+
let _reject;
|
|
538
|
+
this._refreshing = new Promise((res, rej) => {
|
|
539
|
+
resolve = res;
|
|
540
|
+
_reject = rej;
|
|
541
|
+
});
|
|
542
|
+
try {
|
|
543
|
+
const refreshToken = this._storage.get(this._refreshTokenKey);
|
|
544
|
+
if (!refreshToken) {
|
|
545
|
+
await this.logout();
|
|
546
|
+
resolve();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const url = `${this._config.apiPrefix}/refreshSession`;
|
|
550
|
+
const res = await this._http.post(url, null, {
|
|
551
|
+
headers: { Authorization: `Bearer ${refreshToken}` }
|
|
552
|
+
});
|
|
553
|
+
const newToken = res.token ?? res.sessionToken;
|
|
554
|
+
const newRefreshToken = res.refreshToken;
|
|
555
|
+
if (newToken) {
|
|
556
|
+
this._storage.set(this._tokenKey, newToken);
|
|
557
|
+
}
|
|
558
|
+
if (newRefreshToken) {
|
|
559
|
+
this._storage.set(this._refreshTokenKey, newRefreshToken);
|
|
560
|
+
}
|
|
561
|
+
this._emit("tokenRefreshed");
|
|
562
|
+
resolve();
|
|
563
|
+
} catch (_err) {
|
|
564
|
+
await this.logout();
|
|
565
|
+
resolve();
|
|
566
|
+
} finally {
|
|
567
|
+
this._refreshing = null;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Clear tokens and reset session state.
|
|
572
|
+
*/
|
|
573
|
+
async logout() {
|
|
574
|
+
try {
|
|
575
|
+
this._storage.remove(this._tokenKey);
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
this._storage.remove(this._refreshTokenKey);
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
this._setState({ user: null, error: null });
|
|
583
|
+
this._emit("logout");
|
|
584
|
+
this._emit("userChange", null);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Whether a user is currently authenticated.
|
|
588
|
+
*/
|
|
589
|
+
isAuthenticated() {
|
|
590
|
+
return this._state.user !== null;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Restore session from storage on startup.
|
|
594
|
+
*
|
|
595
|
+
* Typically called once during application bootstrap. Sets `initialized`
|
|
596
|
+
* to `true` when complete regardless of outcome.
|
|
597
|
+
*/
|
|
598
|
+
async restore() {
|
|
599
|
+
this._setState({ loading: true });
|
|
600
|
+
try {
|
|
601
|
+
const token = this._storage.get(this._tokenKey);
|
|
602
|
+
if (token) {
|
|
603
|
+
await this.getSession();
|
|
604
|
+
} else {
|
|
605
|
+
this._setState({ user: null });
|
|
606
|
+
}
|
|
607
|
+
} catch {
|
|
608
|
+
} finally {
|
|
609
|
+
this._setState({ initialized: true, loading: false });
|
|
610
|
+
this._emit("userChange", this._state.user);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Start a periodic timer that checks whether the current token is past
|
|
615
|
+
* its midlife point and proactively refreshes it.
|
|
616
|
+
*/
|
|
617
|
+
startAutoCheck() {
|
|
618
|
+
if (this._autoCheckTimer !== null) {
|
|
619
|
+
this.stopAutoCheck();
|
|
620
|
+
}
|
|
621
|
+
this._autoCheckTimer = setInterval(() => {
|
|
622
|
+
this._checkToken();
|
|
623
|
+
}, this._autoCheckInterval);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Stop the periodic auto-check timer.
|
|
627
|
+
*/
|
|
628
|
+
stopAutoCheck() {
|
|
629
|
+
if (this._autoCheckTimer !== null) {
|
|
630
|
+
clearInterval(this._autoCheckTimer);
|
|
631
|
+
this._autoCheckTimer = null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Tear down the manager. Idempotent — safe to call multiple times.
|
|
636
|
+
*/
|
|
637
|
+
destroy() {
|
|
638
|
+
if (this._destroyed) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
this.stopAutoCheck();
|
|
642
|
+
this._listeners.clear();
|
|
643
|
+
this._destroyed = true;
|
|
644
|
+
}
|
|
645
|
+
// =========================================================================
|
|
646
|
+
// Event methods
|
|
647
|
+
// =========================================================================
|
|
648
|
+
/**
|
|
649
|
+
* Register an event handler. Returns `this` for chaining.
|
|
650
|
+
*/
|
|
651
|
+
on(event, handler) {
|
|
652
|
+
let set = this._listeners.get(event);
|
|
653
|
+
if (!set) {
|
|
654
|
+
set = /* @__PURE__ */ new Set();
|
|
655
|
+
this._listeners.set(event, set);
|
|
656
|
+
}
|
|
657
|
+
set.add(handler);
|
|
658
|
+
return this;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Remove a previously registered event handler. Returns `this` for chaining.
|
|
662
|
+
*/
|
|
663
|
+
off(event, handler) {
|
|
664
|
+
const set = this._listeners.get(event);
|
|
665
|
+
if (set) {
|
|
666
|
+
set.delete(handler);
|
|
667
|
+
if (set.size === 0) {
|
|
668
|
+
this._listeners.delete(event);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
// =========================================================================
|
|
674
|
+
// Private helpers
|
|
675
|
+
// =========================================================================
|
|
676
|
+
/**
|
|
677
|
+
* Emit an event to all registered listeners.
|
|
678
|
+
*/
|
|
679
|
+
_emit(event, ...args) {
|
|
680
|
+
const set = this._listeners.get(event);
|
|
681
|
+
if (!set) return;
|
|
682
|
+
for (const handler of set) {
|
|
683
|
+
try {
|
|
684
|
+
handler(...args);
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Merge a partial state update into `_state`.
|
|
691
|
+
*/
|
|
692
|
+
_setState(patch) {
|
|
693
|
+
this._state = { ...this._state, ...patch };
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Direct HTTP call for session — used as the single retry path after
|
|
697
|
+
* refreshSession() to avoid infinite recursion through getSession().
|
|
698
|
+
*/
|
|
699
|
+
async _getSessionDirect() {
|
|
700
|
+
const url = `${this._config.apiPrefix}/session`;
|
|
701
|
+
const token = this._storage.get(this._tokenKey);
|
|
702
|
+
const headers = {};
|
|
703
|
+
if (token) {
|
|
704
|
+
headers.Authorization = `Bearer ${token}`;
|
|
705
|
+
}
|
|
706
|
+
const res = await this._http.get(url, { headers });
|
|
707
|
+
if (res.nextToken) {
|
|
708
|
+
this._storage.set(this._tokenKey, res.nextToken);
|
|
709
|
+
}
|
|
710
|
+
if (res.nextRefreshToken) {
|
|
711
|
+
this._storage.set(this._refreshTokenKey, res.nextRefreshToken);
|
|
712
|
+
}
|
|
713
|
+
if (res.nextToken || res.nextRefreshToken) {
|
|
714
|
+
this._emit("tokenRefreshed");
|
|
715
|
+
}
|
|
716
|
+
const user = res.user ?? null;
|
|
717
|
+
this._setState({ user, error: null });
|
|
718
|
+
this._emit("userChange", user);
|
|
719
|
+
return user;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Check whether the current token is past its midlife and, if so,
|
|
723
|
+
* proactively trigger a refresh.
|
|
724
|
+
*
|
|
725
|
+
* "Midlife" means more than half of the token's lifetime has elapsed:
|
|
726
|
+
* `(exp - now) < (now - iat)`
|
|
727
|
+
*/
|
|
728
|
+
_checkToken() {
|
|
729
|
+
let token;
|
|
730
|
+
try {
|
|
731
|
+
token = this._storage.get(this._tokenKey);
|
|
732
|
+
} catch {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (!token) return;
|
|
736
|
+
const payload = this._decodeJwt(token);
|
|
737
|
+
if (!payload) return;
|
|
738
|
+
const { exp, iat } = payload;
|
|
739
|
+
if (exp == null || iat == null) return;
|
|
740
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
741
|
+
if (exp - now < now - iat) {
|
|
742
|
+
void this.refreshSession();
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Decode a JWT payload without verifying the signature.
|
|
747
|
+
*
|
|
748
|
+
* Security hardening:
|
|
749
|
+
* - Rejects payloads larger than 1 MB after decode (OOM protection)
|
|
750
|
+
* - Strips `__proto__` from parsed object (prototype pollution prevention)
|
|
751
|
+
*
|
|
752
|
+
* @returns Parsed payload or `null` on any failure.
|
|
753
|
+
*/
|
|
754
|
+
_decodeJwt(token) {
|
|
755
|
+
try {
|
|
756
|
+
const parts = token.split(".");
|
|
757
|
+
if (parts.length !== 3) return null;
|
|
758
|
+
const payload = parts[1];
|
|
759
|
+
let base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
760
|
+
const padLength = (4 - base64.length % 4) % 4;
|
|
761
|
+
base64 += "=".repeat(padLength);
|
|
762
|
+
const decoded = atob(base64);
|
|
763
|
+
if (decoded.length > 1048576) return null;
|
|
764
|
+
const parsed = JSON.parse(decoded);
|
|
765
|
+
if ("__proto__" in parsed) {
|
|
766
|
+
delete parsed.__proto__;
|
|
767
|
+
}
|
|
768
|
+
return parsed;
|
|
769
|
+
} catch {
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
function _isUnauthorized(err) {
|
|
775
|
+
if (err == null || typeof err !== "object") return false;
|
|
776
|
+
const e = err;
|
|
777
|
+
if (e.response && typeof e.response === "object" && e.response.status === 401) return true;
|
|
778
|
+
if (e.status === 401) return true;
|
|
779
|
+
if (e.statusCode === 401) return true;
|
|
780
|
+
if (typeof e.message === "string" && /\b401\b/.test(e.message)) return true;
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
function _errorMessage(err) {
|
|
784
|
+
if (err instanceof Error) return err.message;
|
|
785
|
+
if (typeof err === "string") return err;
|
|
786
|
+
return "Unknown error";
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/utils.ts
|
|
790
|
+
function encodeConnectUrl(url) {
|
|
791
|
+
if (url == null) {
|
|
792
|
+
throw new TypeError("encodeConnectUrl: input must not be null or undefined");
|
|
793
|
+
}
|
|
794
|
+
if (url === "") return "";
|
|
795
|
+
const base64 = btoa(unescape(encodeURIComponent(url)));
|
|
796
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
797
|
+
}
|
|
798
|
+
function decodeConnectUrl(encoded) {
|
|
799
|
+
if (encoded == null) {
|
|
800
|
+
throw new TypeError("decodeConnectUrl: input must not be null or undefined");
|
|
801
|
+
}
|
|
802
|
+
if (encoded === "") return "";
|
|
803
|
+
try {
|
|
804
|
+
let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
805
|
+
const pad = base64.length % 4;
|
|
806
|
+
if (pad === 2) base64 += "==";
|
|
807
|
+
else if (pad === 3) base64 += "=";
|
|
808
|
+
else if (pad === 1) base64 += "===";
|
|
809
|
+
return decodeURIComponent(escape(atob(base64)));
|
|
810
|
+
} catch {
|
|
811
|
+
return "";
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function parseTokenFromUrl(url, tokenKey) {
|
|
815
|
+
if (!url) return null;
|
|
816
|
+
const key = tokenKey || DEFAULTS.TOKEN_KEY;
|
|
817
|
+
try {
|
|
818
|
+
const parsed = new URL(url, "http://placeholder");
|
|
819
|
+
return parsed.searchParams.get(key) ?? null;
|
|
820
|
+
} catch {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function parseNextWorkflow(url, tokenKey) {
|
|
825
|
+
if (!url) return null;
|
|
826
|
+
const token = parseTokenFromUrl(url, tokenKey);
|
|
827
|
+
if (!token) return null;
|
|
828
|
+
try {
|
|
829
|
+
const parsed = new URL(url, "http://placeholder");
|
|
830
|
+
const key = tokenKey || DEFAULTS.TOKEN_KEY;
|
|
831
|
+
parsed.searchParams.delete(key);
|
|
832
|
+
const isAbsolute = /^https?:\/\//i.test(url);
|
|
833
|
+
const nextUrl = isAbsolute ? parsed.toString() : `${parsed.pathname}${parsed.search}`;
|
|
834
|
+
return { nextUrl, token };
|
|
835
|
+
} catch {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function getApiErrorMessage(err, defaultMsg) {
|
|
840
|
+
const fallback = defaultMsg || "Unknown error";
|
|
841
|
+
if (!err) return fallback;
|
|
842
|
+
try {
|
|
843
|
+
const data = err?.response?.data;
|
|
844
|
+
if (data) {
|
|
845
|
+
if (typeof data.error === "string" && data.error) return data.error;
|
|
846
|
+
if (typeof data.message === "string" && data.message) return data.message;
|
|
847
|
+
}
|
|
848
|
+
if (typeof err.message === "string" && err.message) return err.message;
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
return fallback;
|
|
852
|
+
}
|
|
853
|
+
var WEBAUTHN_ERROR_MAP = {
|
|
854
|
+
NotAllowedError: "The operation was cancelled or timed out. Please try again.",
|
|
855
|
+
SecurityError: "The operation is not allowed in this context (check your domain or HTTPS settings).",
|
|
856
|
+
InvalidStateError: "This authenticator is already registered. Please use a different one.",
|
|
857
|
+
NotSupportedError: "This browser or device does not support the requested credential type.",
|
|
858
|
+
AbortError: "The operation was aborted.",
|
|
859
|
+
ConstraintError: "The authenticator does not meet the required constraints.",
|
|
860
|
+
UnknownError: "An unknown error occurred with the authenticator.",
|
|
861
|
+
NetworkError: "A network error occurred during the WebAuthn operation.",
|
|
862
|
+
DataError: "The provided data is invalid for a WebAuthn operation.",
|
|
863
|
+
TimeoutError: "The operation timed out. Please try again."
|
|
864
|
+
};
|
|
865
|
+
function getWebAuthnErrorMessage(err, defaultMsg) {
|
|
866
|
+
if (!err) return defaultMsg || "Unknown error";
|
|
867
|
+
const name = err.name;
|
|
868
|
+
if (typeof name === "string" && name in WEBAUTHN_ERROR_MAP) {
|
|
869
|
+
return WEBAUTHN_ERROR_MAP[name];
|
|
870
|
+
}
|
|
871
|
+
return getApiErrorMessage(err, defaultMsg);
|
|
872
|
+
}
|
|
873
|
+
function getBrowserLocale() {
|
|
874
|
+
try {
|
|
875
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
876
|
+
return navigator.language;
|
|
877
|
+
}
|
|
878
|
+
} catch {
|
|
879
|
+
}
|
|
880
|
+
return "en";
|
|
881
|
+
}
|
|
882
|
+
function openPopup(url, options) {
|
|
883
|
+
if (typeof window === "undefined" || typeof window.open !== "function") {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
if (/^\s*javascript\s*:/i.test(url)) {
|
|
887
|
+
throw new Error("openPopup: javascript: URLs are not allowed");
|
|
888
|
+
}
|
|
889
|
+
const width = options?.width ?? 480;
|
|
890
|
+
const height = options?.height ?? 640;
|
|
891
|
+
const left = Math.round((window.screen.width - width) / 2);
|
|
892
|
+
const top = Math.round((window.screen.height - height) / 2);
|
|
893
|
+
const features = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`;
|
|
894
|
+
const popup = window.open(url, "_blank", features);
|
|
895
|
+
return popup ?? null;
|
|
896
|
+
}
|
|
897
|
+
function waitForPopupMessage(popup, options) {
|
|
898
|
+
const timeout = options?.timeout ?? 3e5;
|
|
899
|
+
const origin = options?.origin;
|
|
900
|
+
return new Promise((resolve, reject) => {
|
|
901
|
+
let timer;
|
|
902
|
+
let pollTimer;
|
|
903
|
+
function cleanup() {
|
|
904
|
+
if (typeof window !== "undefined") {
|
|
905
|
+
window.removeEventListener("message", onMessage);
|
|
906
|
+
}
|
|
907
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
908
|
+
if (pollTimer !== void 0) clearInterval(pollTimer);
|
|
909
|
+
}
|
|
910
|
+
function onMessage(event) {
|
|
911
|
+
if (origin && event.origin !== origin) return;
|
|
912
|
+
if (event.source !== popup) return;
|
|
913
|
+
cleanup();
|
|
914
|
+
resolve(event.data);
|
|
915
|
+
}
|
|
916
|
+
if (typeof window === "undefined") {
|
|
917
|
+
reject(new Error("waitForPopupMessage: not in a browser environment"));
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
window.addEventListener("message", onMessage);
|
|
921
|
+
timer = setTimeout(() => {
|
|
922
|
+
cleanup();
|
|
923
|
+
reject(new Error("waitForPopupMessage: timed out"));
|
|
924
|
+
}, timeout);
|
|
925
|
+
pollTimer = setInterval(() => {
|
|
926
|
+
try {
|
|
927
|
+
if (popup.closed) {
|
|
928
|
+
cleanup();
|
|
929
|
+
reject(new Error("waitForPopupMessage: popup was closed"));
|
|
930
|
+
}
|
|
931
|
+
} catch {
|
|
932
|
+
}
|
|
933
|
+
}, 500);
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
function buildActionPrefix(config) {
|
|
937
|
+
const apiPrefix = config.apiPrefix ?? DEFAULTS.API_PREFIX;
|
|
938
|
+
const action = config.action ?? DEFAULTS.ACTION;
|
|
939
|
+
const prefix = apiPrefix.replace(/\/+$/, "");
|
|
940
|
+
const act = action.replace(/^\/+/, "");
|
|
941
|
+
return `${prefix}/${act}`;
|
|
942
|
+
}
|
|
943
|
+
function stringifyParams(params) {
|
|
944
|
+
const parts = [];
|
|
945
|
+
const keys = Object.keys(params);
|
|
946
|
+
for (let i = 0; i < keys.length; i++) {
|
|
947
|
+
const key = keys[i];
|
|
948
|
+
const value = params[key];
|
|
949
|
+
if (value == null) continue;
|
|
950
|
+
if (Array.isArray(value)) {
|
|
951
|
+
for (let j = 0; j < value.length; j++) {
|
|
952
|
+
const item = value[j];
|
|
953
|
+
if (item == null) continue;
|
|
954
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`);
|
|
955
|
+
}
|
|
956
|
+
} else if (typeof value === "boolean") {
|
|
957
|
+
parts.push(`${encodeURIComponent(key)}=${value ? "true" : "false"}`);
|
|
958
|
+
} else {
|
|
959
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return parts.join("&");
|
|
963
|
+
}
|
|
964
|
+
function getConnectedInfo(storage) {
|
|
965
|
+
return {
|
|
966
|
+
did: storage.get(STORAGE_KEYS.CONNECTED_DID) ?? void 0,
|
|
967
|
+
pk: storage.get(STORAGE_KEYS.CONNECTED_PK) ?? void 0,
|
|
968
|
+
app: storage.get(STORAGE_KEYS.CONNECTED_APP) ?? void 0,
|
|
969
|
+
walletOS: storage.get(STORAGE_KEYS.CONNECTED_WALLET_OS) ?? void 0
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/token-session.ts
|
|
974
|
+
function makeInitialState() {
|
|
975
|
+
return {
|
|
976
|
+
token: null,
|
|
977
|
+
url: null,
|
|
978
|
+
status: null,
|
|
979
|
+
error: null,
|
|
980
|
+
appInfo: null,
|
|
981
|
+
memberAppInfo: null,
|
|
982
|
+
loading: false
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
var TokenSession = class {
|
|
986
|
+
// -- State --
|
|
987
|
+
_state;
|
|
988
|
+
_config;
|
|
989
|
+
_http;
|
|
990
|
+
// -- Polling --
|
|
991
|
+
_pollTimer = null;
|
|
992
|
+
_checkCount = 0;
|
|
993
|
+
_maxCheckCount;
|
|
994
|
+
// -- Realtime --
|
|
995
|
+
_realtimeUnsub = null;
|
|
996
|
+
_realtime;
|
|
997
|
+
// -- Storage --
|
|
998
|
+
_storage;
|
|
999
|
+
// -- Throttle --
|
|
1000
|
+
_lastCreateTime = 0;
|
|
1001
|
+
// -- Hand-rolled EventEmitter --
|
|
1002
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
1003
|
+
// -- Lifecycle --
|
|
1004
|
+
_destroyed = false;
|
|
1005
|
+
// --------------------------------------------------------------------------
|
|
1006
|
+
// Constructor
|
|
1007
|
+
// --------------------------------------------------------------------------
|
|
1008
|
+
constructor(config, http, options) {
|
|
1009
|
+
this._config = this._fillDefaults(config);
|
|
1010
|
+
this._http = http;
|
|
1011
|
+
this._realtime = options?.realtime;
|
|
1012
|
+
this._storage = options?.storage;
|
|
1013
|
+
this._state = makeInitialState();
|
|
1014
|
+
if (this._config.checkInterval <= 0) {
|
|
1015
|
+
this._maxCheckCount = Infinity;
|
|
1016
|
+
} else {
|
|
1017
|
+
this._maxCheckCount = Math.ceil(
|
|
1018
|
+
this._config.tokenTimeout / this._config.checkInterval
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
// --------------------------------------------------------------------------
|
|
1023
|
+
// Public getters
|
|
1024
|
+
// --------------------------------------------------------------------------
|
|
1025
|
+
/** Return a shallow read-only copy of the current state. */
|
|
1026
|
+
get state() {
|
|
1027
|
+
return Object.freeze({ ...this._state });
|
|
1028
|
+
}
|
|
1029
|
+
// --------------------------------------------------------------------------
|
|
1030
|
+
// Public methods
|
|
1031
|
+
// --------------------------------------------------------------------------
|
|
1032
|
+
/**
|
|
1033
|
+
* Create a new connect token via the server.
|
|
1034
|
+
*
|
|
1035
|
+
* Throttled: calls within 500 ms of each other are silently ignored.
|
|
1036
|
+
* Any active polling is stopped before the request is made.
|
|
1037
|
+
*/
|
|
1038
|
+
async create(params) {
|
|
1039
|
+
if (Date.now() - this._lastCreateTime < DEFAULTS.CREATE_THROTTLE) {
|
|
1040
|
+
return this.state;
|
|
1041
|
+
}
|
|
1042
|
+
if (this._pollTimer !== null || this._realtimeUnsub !== null) {
|
|
1043
|
+
this.stopPolling();
|
|
1044
|
+
}
|
|
1045
|
+
this._state.loading = true;
|
|
1046
|
+
this._emit("statusChange", this.state);
|
|
1047
|
+
const url = `${buildActionPrefix(this._config)}/token`;
|
|
1048
|
+
const queryParams = {};
|
|
1049
|
+
if (params) {
|
|
1050
|
+
if (params.locale != null) queryParams.locale = params.locale;
|
|
1051
|
+
if (params.provider != null) {
|
|
1052
|
+
queryParams.provider = params.provider;
|
|
1053
|
+
} else {
|
|
1054
|
+
queryParams.provider = "wallet";
|
|
1055
|
+
}
|
|
1056
|
+
if (params.encKey != null) queryParams.__encKey = params.encKey;
|
|
1057
|
+
if (params.autoConnect != null) queryParams.autoConnect = params.autoConnect;
|
|
1058
|
+
if (params.visitorId != null) queryParams.visitorId = params.visitorId;
|
|
1059
|
+
if (params.sourceToken != null) queryParams.sourceToken = params.sourceToken;
|
|
1060
|
+
if (params.forceConnected != null) queryParams.forceConnected = params.forceConnected;
|
|
1061
|
+
if (params.connectUrl != null) queryParams.connectUrl = params.connectUrl;
|
|
1062
|
+
if (params.extraParams) {
|
|
1063
|
+
Object.assign(queryParams, params.extraParams);
|
|
1064
|
+
}
|
|
1065
|
+
} else {
|
|
1066
|
+
queryParams.provider = "wallet";
|
|
1067
|
+
}
|
|
1068
|
+
try {
|
|
1069
|
+
const response = await this._http.get(url, {
|
|
1070
|
+
params: queryParams
|
|
1071
|
+
});
|
|
1072
|
+
this._state.token = response.token ?? null;
|
|
1073
|
+
this._state.url = response.url ?? null;
|
|
1074
|
+
this._state.status = TOKEN_STATUS.CREATED;
|
|
1075
|
+
this._state.appInfo = response.appInfo ?? null;
|
|
1076
|
+
this._state.memberAppInfo = response.memberAppInfo ?? null;
|
|
1077
|
+
this._state.loading = false;
|
|
1078
|
+
this._state.error = null;
|
|
1079
|
+
this._state.successResult = void 0;
|
|
1080
|
+
this._state.connectedDid = void 0;
|
|
1081
|
+
this._state.mfaCode = void 0;
|
|
1082
|
+
this._state.saveConnect = void 0;
|
|
1083
|
+
this._lastCreateTime = Date.now();
|
|
1084
|
+
this._emit("statusChange", this.state);
|
|
1085
|
+
return this.state;
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
this._state.loading = false;
|
|
1088
|
+
this._state.error = err?.message ?? "Failed to create token";
|
|
1089
|
+
this._state.status = TOKEN_STATUS.ERROR;
|
|
1090
|
+
this._emit("statusChange", this.state);
|
|
1091
|
+
this._emit("error", new ConnectError(ERROR_CODES.NETWORK_ERROR, this._state.error));
|
|
1092
|
+
throw err;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Check the current token status against the server.
|
|
1097
|
+
*
|
|
1098
|
+
* Throws if no token has been created yet.
|
|
1099
|
+
*/
|
|
1100
|
+
async checkStatus() {
|
|
1101
|
+
if (!this._state.token) {
|
|
1102
|
+
throw new ConnectError(
|
|
1103
|
+
ERROR_CODES.TOKEN_NOT_FOUND,
|
|
1104
|
+
"No token available. Call create() first."
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
const url = `${buildActionPrefix(this._config)}/status`;
|
|
1108
|
+
const params = {
|
|
1109
|
+
[DEFAULTS.TOKEN_KEY]: this._state.token
|
|
1110
|
+
};
|
|
1111
|
+
if (this._config.appPid) {
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const response = await this._http.get(url, { params });
|
|
1115
|
+
const status = response.status;
|
|
1116
|
+
if (status) this._state.status = status;
|
|
1117
|
+
if (response.error !== void 0) this._state.error = response.error;
|
|
1118
|
+
if (response.connectedDid !== void 0) this._state.connectedDid = response.connectedDid;
|
|
1119
|
+
if (response.mfaCode !== void 0) this._state.mfaCode = response.mfaCode;
|
|
1120
|
+
if (response.saveConnect !== void 0) this._state.saveConnect = response.saveConnect;
|
|
1121
|
+
if (status === TOKEN_STATUS.FORBIDDEN) {
|
|
1122
|
+
this._state.status = TOKEN_STATUS.ERROR;
|
|
1123
|
+
this._state.error = response.error || "Access forbidden";
|
|
1124
|
+
this._emit("error", new ConnectError(ERROR_CODES.UNAUTHORIZED, this._state.error));
|
|
1125
|
+
this.stopPolling();
|
|
1126
|
+
} else if (status === TOKEN_STATUS.SUCCEED) {
|
|
1127
|
+
if (this._config.tokenDelivery === "response") {
|
|
1128
|
+
this._state.successResult = response;
|
|
1129
|
+
}
|
|
1130
|
+
this._emit("succeed", this.state);
|
|
1131
|
+
this.stopPolling();
|
|
1132
|
+
} else if (status === TOKEN_STATUS.ERROR) {
|
|
1133
|
+
this._emit(
|
|
1134
|
+
"error",
|
|
1135
|
+
new ConnectError(ERROR_CODES.INVALID_RESPONSE, this._state.error || "Unknown error")
|
|
1136
|
+
);
|
|
1137
|
+
this.stopPolling();
|
|
1138
|
+
} else if (status === TOKEN_STATUS.TIMEOUT) {
|
|
1139
|
+
this._emit("timeout", this.state);
|
|
1140
|
+
this.stopPolling();
|
|
1141
|
+
}
|
|
1142
|
+
this._emit("statusChange", this.state);
|
|
1143
|
+
return this.state;
|
|
1144
|
+
} catch (err) {
|
|
1145
|
+
throw err;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Cancel the current token by notifying the server, then stop polling.
|
|
1150
|
+
*
|
|
1151
|
+
* Silently returns if there is no active token.
|
|
1152
|
+
*/
|
|
1153
|
+
async cancel() {
|
|
1154
|
+
if (!this._state.token) return;
|
|
1155
|
+
const url = `${buildActionPrefix(this._config)}/timeout`;
|
|
1156
|
+
const params = {
|
|
1157
|
+
[DEFAULTS.TOKEN_KEY]: this._state.token
|
|
1158
|
+
};
|
|
1159
|
+
try {
|
|
1160
|
+
await this._http.get(url, { params });
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
this.stopPolling();
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Begin polling for status updates.
|
|
1167
|
+
*
|
|
1168
|
+
* If a RealtimeAdapter was provided, subscribes to the realtime channel
|
|
1169
|
+
* instead of interval polling. Falls back to interval on subscription error.
|
|
1170
|
+
*/
|
|
1171
|
+
startPolling() {
|
|
1172
|
+
if (!this._state.token) return;
|
|
1173
|
+
this._checkCount = 0;
|
|
1174
|
+
if (this._realtime) {
|
|
1175
|
+
try {
|
|
1176
|
+
const channel = `relay:${this._config.appPid}:${this._state.token}`;
|
|
1177
|
+
this._realtimeUnsub = this._realtime.subscribe(channel, (data) => {
|
|
1178
|
+
this._handleRealtimeMessage(data);
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
} catch {
|
|
1182
|
+
this._realtimeUnsub = null;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (this._config.checkInterval > 0) {
|
|
1186
|
+
this._pollTimer = setInterval(() => {
|
|
1187
|
+
this._pollOnce();
|
|
1188
|
+
}, this._config.checkInterval);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Stop all polling — clears interval timer and realtime subscription.
|
|
1193
|
+
*/
|
|
1194
|
+
stopPolling() {
|
|
1195
|
+
if (this._pollTimer !== null) {
|
|
1196
|
+
clearInterval(this._pollTimer);
|
|
1197
|
+
this._pollTimer = null;
|
|
1198
|
+
}
|
|
1199
|
+
if (this._realtimeUnsub) {
|
|
1200
|
+
this._realtimeUnsub();
|
|
1201
|
+
this._realtimeUnsub = null;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Reset the session to its initial state.
|
|
1206
|
+
*
|
|
1207
|
+
* Stops polling and clears all token data, but preserves event listeners.
|
|
1208
|
+
*/
|
|
1209
|
+
reset() {
|
|
1210
|
+
this.stopPolling();
|
|
1211
|
+
this._state = makeInitialState();
|
|
1212
|
+
this._checkCount = 0;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Permanently tear down this session.
|
|
1216
|
+
*
|
|
1217
|
+
* Idempotent — subsequent calls are no-ops. Clears listeners, stops polling,
|
|
1218
|
+
* and nulls out token data.
|
|
1219
|
+
*/
|
|
1220
|
+
destroy() {
|
|
1221
|
+
if (this._destroyed) return;
|
|
1222
|
+
this.stopPolling();
|
|
1223
|
+
this._state.token = null;
|
|
1224
|
+
this._state.successResult = void 0;
|
|
1225
|
+
this._listeners.clear();
|
|
1226
|
+
this._destroyed = true;
|
|
1227
|
+
}
|
|
1228
|
+
// --------------------------------------------------------------------------
|
|
1229
|
+
// Event methods (hand-rolled EventEmitter)
|
|
1230
|
+
// --------------------------------------------------------------------------
|
|
1231
|
+
/**
|
|
1232
|
+
* Register an event handler. Returns `this` for chaining.
|
|
1233
|
+
*
|
|
1234
|
+
* Events:
|
|
1235
|
+
* - `statusChange` — fired on every state mutation, payload: `TokenState`
|
|
1236
|
+
* - `succeed` — token auth completed successfully, payload: `TokenState`
|
|
1237
|
+
* - `error` — an error occurred, payload: `ConnectError`
|
|
1238
|
+
* - `timeout` — token or poll timed out, payload: `TokenState`
|
|
1239
|
+
*/
|
|
1240
|
+
on(event, handler) {
|
|
1241
|
+
let set = this._listeners.get(event);
|
|
1242
|
+
if (!set) {
|
|
1243
|
+
set = /* @__PURE__ */ new Set();
|
|
1244
|
+
this._listeners.set(event, set);
|
|
1245
|
+
}
|
|
1246
|
+
set.add(handler);
|
|
1247
|
+
return this;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Remove an event handler. Returns `this` for chaining.
|
|
1251
|
+
*/
|
|
1252
|
+
off(event, handler) {
|
|
1253
|
+
const set = this._listeners.get(event);
|
|
1254
|
+
if (set) {
|
|
1255
|
+
set.delete(handler);
|
|
1256
|
+
if (set.size === 0) {
|
|
1257
|
+
this._listeners.delete(event);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return this;
|
|
1261
|
+
}
|
|
1262
|
+
// --------------------------------------------------------------------------
|
|
1263
|
+
// Private methods
|
|
1264
|
+
// --------------------------------------------------------------------------
|
|
1265
|
+
/** Emit an event to all registered handlers. */
|
|
1266
|
+
_emit(event, ...args) {
|
|
1267
|
+
const set = this._listeners.get(event);
|
|
1268
|
+
if (!set || set.size === 0) return;
|
|
1269
|
+
for (const handler of [...set]) {
|
|
1270
|
+
try {
|
|
1271
|
+
handler(...args);
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
/** Single poll tick — called by setInterval. */
|
|
1277
|
+
_pollOnce() {
|
|
1278
|
+
this._checkCount++;
|
|
1279
|
+
if (this._checkCount >= this._maxCheckCount) {
|
|
1280
|
+
this._handleTimeout();
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
this.checkStatus().catch(() => {
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
/** Handle poll timeout (max iterations reached). */
|
|
1287
|
+
_handleTimeout() {
|
|
1288
|
+
this.stopPolling();
|
|
1289
|
+
this._state.status = TOKEN_STATUS.TIMEOUT;
|
|
1290
|
+
this._emit("timeout", this.state);
|
|
1291
|
+
this._emit("statusChange", this.state);
|
|
1292
|
+
}
|
|
1293
|
+
/** Handle an incoming realtime message for the subscribed token channel. */
|
|
1294
|
+
_handleRealtimeMessage(data) {
|
|
1295
|
+
if (!data || typeof data !== "object") return;
|
|
1296
|
+
const status = data.status;
|
|
1297
|
+
if (!status) return;
|
|
1298
|
+
this._state.status = status;
|
|
1299
|
+
if (data.error !== void 0) this._state.error = data.error;
|
|
1300
|
+
if (data.connectedDid !== void 0) this._state.connectedDid = data.connectedDid;
|
|
1301
|
+
if (data.mfaCode !== void 0) this._state.mfaCode = data.mfaCode;
|
|
1302
|
+
if (data.saveConnect !== void 0) this._state.saveConnect = data.saveConnect;
|
|
1303
|
+
if (status === TOKEN_STATUS.FORBIDDEN) {
|
|
1304
|
+
this._state.status = TOKEN_STATUS.ERROR;
|
|
1305
|
+
this._state.error = data.error || "Access forbidden";
|
|
1306
|
+
this._emit("error", new ConnectError(ERROR_CODES.UNAUTHORIZED, this._state.error));
|
|
1307
|
+
this.stopPolling();
|
|
1308
|
+
} else if (status === TOKEN_STATUS.SUCCEED) {
|
|
1309
|
+
if (this._config.tokenDelivery === "response") {
|
|
1310
|
+
this._state.successResult = data;
|
|
1311
|
+
}
|
|
1312
|
+
this._emit("succeed", this.state);
|
|
1313
|
+
this.stopPolling();
|
|
1314
|
+
} else if (status === TOKEN_STATUS.ERROR) {
|
|
1315
|
+
this._emit(
|
|
1316
|
+
"error",
|
|
1317
|
+
new ConnectError(ERROR_CODES.INVALID_RESPONSE, this._state.error || "Unknown error")
|
|
1318
|
+
);
|
|
1319
|
+
this.stopPolling();
|
|
1320
|
+
} else if (status === TOKEN_STATUS.TIMEOUT) {
|
|
1321
|
+
this._emit("timeout", this.state);
|
|
1322
|
+
this.stopPolling();
|
|
1323
|
+
}
|
|
1324
|
+
this._emit("statusChange", this.state);
|
|
1325
|
+
}
|
|
1326
|
+
/** Fill optional ConnectConfig fields with DEFAULTS. */
|
|
1327
|
+
_fillDefaults(config) {
|
|
1328
|
+
return {
|
|
1329
|
+
appPid: config.appPid,
|
|
1330
|
+
appName: config.appName,
|
|
1331
|
+
appDescription: config.appDescription ?? "",
|
|
1332
|
+
appIcon: config.appIcon ?? "",
|
|
1333
|
+
appUrl: config.appUrl ?? "",
|
|
1334
|
+
apiPrefix: config.apiPrefix ?? DEFAULTS.API_PREFIX,
|
|
1335
|
+
action: config.action ?? DEFAULTS.ACTION,
|
|
1336
|
+
servicePrefix: config.servicePrefix ?? DEFAULTS.SERVICE_PREFIX,
|
|
1337
|
+
tokenTimeout: config.tokenTimeout ?? DEFAULTS.TOKEN_TIMEOUT,
|
|
1338
|
+
checkInterval: config.checkInterval ?? DEFAULTS.CHECK_INTERVAL,
|
|
1339
|
+
tokenDelivery: config.tokenDelivery ?? DEFAULTS.TOKEN_DELIVERY
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// src/index.ts
|
|
1345
|
+
var VERSION = "4.0.0-beta.0";
|
|
1346
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1347
|
+
0 && (module.exports = {
|
|
1348
|
+
ConnectError,
|
|
1349
|
+
DEFAULTS,
|
|
1350
|
+
DID_PREFIX,
|
|
1351
|
+
ERROR_CODES,
|
|
1352
|
+
EmailFlow,
|
|
1353
|
+
FetchHttpAdapter,
|
|
1354
|
+
OAuthFlow,
|
|
1355
|
+
PasskeyFlow,
|
|
1356
|
+
STORAGE_KEYS,
|
|
1357
|
+
SessionManager,
|
|
1358
|
+
TERMINAL_STATUSES,
|
|
1359
|
+
TOKEN_STATUS,
|
|
1360
|
+
TokenSession,
|
|
1361
|
+
VERSION,
|
|
1362
|
+
buildActionPrefix,
|
|
1363
|
+
decodeConnectUrl,
|
|
1364
|
+
encodeConnectUrl,
|
|
1365
|
+
getApiErrorMessage,
|
|
1366
|
+
getBrowserLocale,
|
|
1367
|
+
getConnectedInfo,
|
|
1368
|
+
getDidChainLabel,
|
|
1369
|
+
getDidDisplayParts,
|
|
1370
|
+
getWebAuthnErrorMessage,
|
|
1371
|
+
isEthereumDid,
|
|
1372
|
+
openPopup,
|
|
1373
|
+
parseNextWorkflow,
|
|
1374
|
+
parseTokenFromUrl,
|
|
1375
|
+
stringifyParams,
|
|
1376
|
+
truncateDid,
|
|
1377
|
+
waitForPopupMessage
|
|
1378
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcblock/did-connect-core",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"description": "Framework-agnostic DID Connect protocol client — types, state machines, adapters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
12
13
|
},
|
|
13
14
|
"./ui": {
|
|
14
15
|
"types": "./dist/ui/index.d.ts",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"qrcode-generator": "^2.0.4"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
|
-
"build": "tsc && node build-ui.mjs",
|
|
33
|
+
"build": "tsc && node build-ui.mjs && esbuild src/index.ts --bundle --format=cjs --platform=node --target=node20 --outfile=dist/index.cjs",
|
|
33
34
|
"lint": "biome check src/",
|
|
34
35
|
"test": "vitest run",
|
|
35
36
|
"test:coverage": "vitest run --coverage",
|