decane-connect-kit 2.29.3 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +170 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -17
- package/dist/index.d.ts +79 -17
- package/dist/index.js +169 -115
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __export(errors_exports, {
|
|
|
42
42
|
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
43
43
|
InvalidPinError: () => InvalidPinError,
|
|
44
44
|
InvalidRecoveryFileError: () => InvalidRecoveryFileError,
|
|
45
|
+
LoginInUseError: () => LoginInUseError,
|
|
45
46
|
NetworkError: () => NetworkError,
|
|
46
47
|
NewDeviceError: () => NewDeviceError,
|
|
47
48
|
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
@@ -64,7 +65,7 @@ __export(errors_exports, {
|
|
|
64
65
|
WrongPasswordError: () => WrongPasswordError,
|
|
65
66
|
WrongUnlockPasswordError: () => WrongUnlockPasswordError
|
|
66
67
|
});
|
|
67
|
-
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError, WalletExistsError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
|
|
68
|
+
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError, LoginInUseError, WalletExistsError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
|
|
68
69
|
var init_errors = __esm({
|
|
69
70
|
"src/social/errors.ts"() {
|
|
70
71
|
"use strict";
|
|
@@ -213,6 +214,11 @@ var init_errors = __esm({
|
|
|
213
214
|
super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
|
|
214
215
|
}
|
|
215
216
|
};
|
|
217
|
+
LoginInUseError = class extends DecaneConnectError {
|
|
218
|
+
constructor(message = "That login already opens another account. It was not linked.") {
|
|
219
|
+
super("LOGIN_IN_USE", message);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
216
222
|
WalletExistsError = class extends DecaneConnectError {
|
|
217
223
|
constructor() {
|
|
218
224
|
super("WALLET_EXISTS", "This account already has a wallet; opening it instead.", true);
|
|
@@ -266,6 +272,7 @@ __export(index_exports, {
|
|
|
266
272
|
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
267
273
|
InvalidPinError: () => InvalidPinError,
|
|
268
274
|
InvalidRecoveryFileError: () => InvalidRecoveryFileError,
|
|
275
|
+
LoginInUseError: () => LoginInUseError,
|
|
269
276
|
MIN_PASSWORD_LENGTH: () => MIN_PASSWORD_LENGTH,
|
|
270
277
|
NetworkError: () => NetworkError,
|
|
271
278
|
NewDeviceError: () => NewDeviceError,
|
|
@@ -2118,7 +2125,9 @@ var ERROR_MAP = {
|
|
|
2118
2125
|
// generic failure message.
|
|
2119
2126
|
UNAUTHORIZED: ReconnectRequiredError,
|
|
2120
2127
|
// A restorable wallet already exists: the client provisions instead of creating.
|
|
2121
|
-
WALLET_EXISTS: WalletExistsError
|
|
2128
|
+
WALLET_EXISTS: WalletExistsError,
|
|
2129
|
+
// linkWithToken(): the login already opens another account.
|
|
2130
|
+
LOGIN_IN_USE: LoginInUseError
|
|
2122
2131
|
};
|
|
2123
2132
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
2124
2133
|
var ApiClient = class {
|
|
@@ -2185,9 +2194,19 @@ var ApiClient = class {
|
|
|
2185
2194
|
return this._request("/auth/kingschat/config", { method: "GET" });
|
|
2186
2195
|
}
|
|
2187
2196
|
/**
|
|
2188
|
-
* Exchange a KingsChat
|
|
2189
|
-
*
|
|
2190
|
-
*
|
|
2197
|
+
* Exchange a KingsChat authorization code for a Decane session. The backend
|
|
2198
|
+
* trades it with KingsChat for an access token, verifies that token (it must
|
|
2199
|
+
* have been issued to this project's KingsChat app), and never stores it.
|
|
2200
|
+
*/
|
|
2201
|
+
async authKingsChatCode(code) {
|
|
2202
|
+
return this._request("/auth/kingschat/code", {
|
|
2203
|
+
method: "POST",
|
|
2204
|
+
body: JSON.stringify({ code })
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Exchange a KingsChat access token the app already holds for a Decane
|
|
2209
|
+
* session. Verified the same way as a code's token, and never stored.
|
|
2191
2210
|
*/
|
|
2192
2211
|
async authKingsChat(accessToken) {
|
|
2193
2212
|
return this._request("/auth/kingschat", {
|
|
@@ -2287,6 +2306,23 @@ var ApiClient = class {
|
|
|
2287
2306
|
body: JSON.stringify(providerId ? { token, providerId } : { token })
|
|
2288
2307
|
});
|
|
2289
2308
|
}
|
|
2309
|
+
/** Attach a login from the project's own provider to the signed-in account. */
|
|
2310
|
+
async linkExternal(jwt, token, providerId) {
|
|
2311
|
+
return this._request("/auth/link/external", {
|
|
2312
|
+
method: "POST",
|
|
2313
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
2314
|
+
body: JSON.stringify(providerId ? { token, providerId } : { token })
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
async listLinkedLogins(jwt) {
|
|
2318
|
+
return this._request("/auth/link/external", { method: "GET", headers: { Authorization: `Bearer ${jwt}` } });
|
|
2319
|
+
}
|
|
2320
|
+
async unlinkLogin(jwt, id) {
|
|
2321
|
+
await this._request(`/auth/link/external/${encodeURIComponent(id)}`, {
|
|
2322
|
+
method: "DELETE",
|
|
2323
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2290
2326
|
/**
|
|
2291
2327
|
* Returns null when this user has no server-registered passkey, which is the
|
|
2292
2328
|
* expected answer for wallets created before passkey sign-in existed or for
|
|
@@ -3345,75 +3381,47 @@ async function verifyPhoneCode(api, phone, code) {
|
|
|
3345
3381
|
|
|
3346
3382
|
// src/social/auth/kingschat.ts
|
|
3347
3383
|
init_errors();
|
|
3348
|
-
var
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
const
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
}
|
|
3384
|
+
var KINGSCHAT_LOGIN_URL = "https://accounts.kingschat.online/log-in";
|
|
3385
|
+
var STATE_KEY = "decane:kingschat_state";
|
|
3386
|
+
function randomState() {
|
|
3387
|
+
const bytes = new Uint8Array(16);
|
|
3388
|
+
crypto.getRandomValues(bytes);
|
|
3389
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3390
|
+
}
|
|
3391
|
+
function storage2() {
|
|
3392
|
+
try {
|
|
3393
|
+
return window.sessionStorage ?? null;
|
|
3394
|
+
} catch {
|
|
3395
|
+
return null;
|
|
3396
|
+
}
|
|
3361
3397
|
}
|
|
3362
|
-
function
|
|
3363
|
-
const
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
url
|
|
3398
|
+
function beginKingsChatLogin(clientId, opts = {}) {
|
|
3399
|
+
const store = storage2();
|
|
3400
|
+
if (!store) throw new Error("KingsChat sign-in needs sessionStorage, which this browser is blocking");
|
|
3401
|
+
const state = randomState();
|
|
3402
|
+
store.setItem(STATE_KEY, state);
|
|
3403
|
+
const url = new URL(KINGSCHAT_LOGIN_URL);
|
|
3404
|
+
url.searchParams.set("clientId", clientId);
|
|
3405
|
+
url.searchParams.set("origin", state);
|
|
3406
|
+
if (opts.forceLogin) url.searchParams.set("forceLogin", "true");
|
|
3368
3407
|
return url.toString();
|
|
3369
3408
|
}
|
|
3370
|
-
function
|
|
3371
|
-
const
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3409
|
+
function readKingsChatReturn(search) {
|
|
3410
|
+
const params = new URLSearchParams(search);
|
|
3411
|
+
const code = params.get("code");
|
|
3412
|
+
const origin = params.get("origin");
|
|
3413
|
+
if (!code || !origin) return null;
|
|
3414
|
+
const expected = storage2()?.getItem(STATE_KEY);
|
|
3415
|
+
if (!expected || expected !== origin) return null;
|
|
3416
|
+
return { code };
|
|
3417
|
+
}
|
|
3418
|
+
function clearKingsChatState() {
|
|
3419
|
+
storage2()?.removeItem(STATE_KEY);
|
|
3420
|
+
}
|
|
3421
|
+
async function loginWithKingsChatPopup(_opts) {
|
|
3422
|
+
throw new CallSequenceError(
|
|
3423
|
+
"loginWithKingsChatPopup was removed in 2.31.0: KingsChat retired its popup login. Use connectWithKingsChat(), which redirects to KingsChat and finishes on your registered Redirect URL."
|
|
3376
3424
|
);
|
|
3377
|
-
if (!popup) {
|
|
3378
|
-
throw new CallSequenceError(
|
|
3379
|
-
"The KingsChat sign-in popup was blocked. Call connectWithKingsChat() directly from a click handler."
|
|
3380
|
-
);
|
|
3381
|
-
}
|
|
3382
|
-
return new Promise((resolve, reject) => {
|
|
3383
|
-
let settled = false;
|
|
3384
|
-
const finish = (fn) => {
|
|
3385
|
-
if (settled) return;
|
|
3386
|
-
settled = true;
|
|
3387
|
-
window.removeEventListener("message", onMessage);
|
|
3388
|
-
clearInterval(closedPoll);
|
|
3389
|
-
clearTimeout(timeout);
|
|
3390
|
-
try {
|
|
3391
|
-
popup.close();
|
|
3392
|
-
} catch {
|
|
3393
|
-
}
|
|
3394
|
-
fn();
|
|
3395
|
-
};
|
|
3396
|
-
const onMessage = (event) => {
|
|
3397
|
-
if (event.origin !== KINGSCHAT_AUTH_ORIGIN) return;
|
|
3398
|
-
const data = event.data;
|
|
3399
|
-
if (data && typeof data === "object" && data.error) {
|
|
3400
|
-
finish(() => reject(new CallSequenceError(`KingsChat sign-in failed: ${String(data.error)}`)));
|
|
3401
|
-
return;
|
|
3402
|
-
}
|
|
3403
|
-
const tokens2 = readTokens(event.data);
|
|
3404
|
-
if (tokens2) finish(() => resolve(tokens2));
|
|
3405
|
-
};
|
|
3406
|
-
window.addEventListener("message", onMessage);
|
|
3407
|
-
const closedPoll = setInterval(() => {
|
|
3408
|
-
if (popup.closed) finish(() => reject(new UserCancelledError()));
|
|
3409
|
-
}, 400);
|
|
3410
|
-
const timeout = setTimeout(
|
|
3411
|
-
() => finish(
|
|
3412
|
-
() => reject(new CallSequenceError("KingsChat sign-in timed out before returning a token."))
|
|
3413
|
-
),
|
|
3414
|
-
opts.timeoutMs ?? 5 * 6e4
|
|
3415
|
-
);
|
|
3416
|
-
});
|
|
3417
3425
|
}
|
|
3418
3426
|
|
|
3419
3427
|
// src/social/signers/evm.ts
|
|
@@ -4018,7 +4026,7 @@ async function createDecaneConnect(config) {
|
|
|
4018
4026
|
const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
|
|
4019
4027
|
const events = new EventEmitter();
|
|
4020
4028
|
const api = new ApiClient(baseUrl, config.apiKey, config.appId);
|
|
4021
|
-
const
|
|
4029
|
+
const storage3 = new Storage();
|
|
4022
4030
|
const session = new SessionManager(
|
|
4023
4031
|
events,
|
|
4024
4032
|
durationMs,
|
|
@@ -4208,7 +4216,7 @@ async function createDecaneConnect(config) {
|
|
|
4208
4216
|
}
|
|
4209
4217
|
async function attachUnlockToken(userId, jwt, tokenKey) {
|
|
4210
4218
|
try {
|
|
4211
|
-
const stored = await
|
|
4219
|
+
const stored = await storage3.getShare(userId);
|
|
4212
4220
|
if (!stored) return;
|
|
4213
4221
|
const { token } = await api.issueUnlockToken(jwt);
|
|
4214
4222
|
const unlockTokenIv = generateRandomBytes(12);
|
|
@@ -4217,13 +4225,13 @@ async function createDecaneConnect(config) {
|
|
|
4217
4225
|
new TextEncoder().encode(token),
|
|
4218
4226
|
unlockTokenIv
|
|
4219
4227
|
);
|
|
4220
|
-
await
|
|
4228
|
+
await storage3.putShare({ ...stored, wrappedUnlockToken, unlockTokenIv });
|
|
4221
4229
|
} catch (e) {
|
|
4222
4230
|
console.warn("decane-connect-kit: could not store an unlock token for this device.", e);
|
|
4223
4231
|
}
|
|
4224
4232
|
}
|
|
4225
4233
|
async function persistRotatedDeviceShare(userId, deviceShare) {
|
|
4226
|
-
const stored = await
|
|
4234
|
+
const stored = await storage3.getShare(userId);
|
|
4227
4235
|
if (!stored) {
|
|
4228
4236
|
throw new CallSequenceError(
|
|
4229
4237
|
"Share set was rotated but this device has no stored share to update. Recover the wallet on this device before rotating."
|
|
@@ -4232,21 +4240,21 @@ async function createDecaneConnect(config) {
|
|
|
4232
4240
|
const wrappingKey = await getWrappingKey(stored);
|
|
4233
4241
|
const iv = generateRandomBytes(12);
|
|
4234
4242
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4235
|
-
await
|
|
4243
|
+
await storage3.putShare({ ...stored, wrappedDeviceShare, iv });
|
|
4236
4244
|
}
|
|
4237
4245
|
function hasPasswordUnlock(stored) {
|
|
4238
4246
|
return !!stored && stored.unlockMethod === "password" && !!stored.passwordSalt && !!stored.wrappedUnlockToken && !!stored.unlockTokenIv;
|
|
4239
4247
|
}
|
|
4240
4248
|
async function passwordDeviceCandidate() {
|
|
4241
4249
|
if (persisted?.userId) {
|
|
4242
|
-
const stored = await
|
|
4250
|
+
const stored = await storage3.getShare(persisted.userId);
|
|
4243
4251
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
4244
4252
|
}
|
|
4245
4253
|
if (lastUserId) {
|
|
4246
|
-
const stored = await
|
|
4254
|
+
const stored = await storage3.getShare(lastUserId);
|
|
4247
4255
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
4248
4256
|
}
|
|
4249
|
-
const candidates = (await
|
|
4257
|
+
const candidates = (await storage3.listShares()).filter(hasPasswordUnlock);
|
|
4250
4258
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4251
4259
|
}
|
|
4252
4260
|
async function getWrappingKey(stored) {
|
|
@@ -4274,7 +4282,7 @@ async function createDecaneConnect(config) {
|
|
|
4274
4282
|
let credentialId;
|
|
4275
4283
|
let prfOutput;
|
|
4276
4284
|
try {
|
|
4277
|
-
const label = (await
|
|
4285
|
+
const label = (await storage3.getShare(userId))?.profile?.email;
|
|
4278
4286
|
({ credentialId, prfOutput } = await registerWebAuthnCredential({
|
|
4279
4287
|
api,
|
|
4280
4288
|
jwt,
|
|
@@ -4306,8 +4314,8 @@ async function createDecaneConnect(config) {
|
|
|
4306
4314
|
const wrappingKey = await importAesKey(prfOutput);
|
|
4307
4315
|
const iv = generateRandomBytes(12);
|
|
4308
4316
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4309
|
-
const existing = await
|
|
4310
|
-
await
|
|
4317
|
+
const existing = await storage3.getShare(userId);
|
|
4318
|
+
await storage3.putShare({
|
|
4311
4319
|
...existing,
|
|
4312
4320
|
userId,
|
|
4313
4321
|
wrappedDeviceShare,
|
|
@@ -4391,7 +4399,7 @@ async function createDecaneConnect(config) {
|
|
|
4391
4399
|
const { wrappingKey, tokenKey, meta } = await passwordSetup();
|
|
4392
4400
|
const iv = generateRandomBytes(12);
|
|
4393
4401
|
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
4394
|
-
await
|
|
4402
|
+
await storage3.putShare({
|
|
4395
4403
|
userId,
|
|
4396
4404
|
wrappedDeviceShare,
|
|
4397
4405
|
iv,
|
|
@@ -4465,7 +4473,7 @@ async function createDecaneConnect(config) {
|
|
|
4465
4473
|
shareSet.device,
|
|
4466
4474
|
iv
|
|
4467
4475
|
);
|
|
4468
|
-
await
|
|
4476
|
+
await storage3.putShare({
|
|
4469
4477
|
userId,
|
|
4470
4478
|
wrappedDeviceShare,
|
|
4471
4479
|
iv,
|
|
@@ -4505,7 +4513,7 @@ async function createDecaneConnect(config) {
|
|
|
4505
4513
|
zeroize(shareSet.server);
|
|
4506
4514
|
zeroize(shareSet.device);
|
|
4507
4515
|
zeroize(shareSet.recovery);
|
|
4508
|
-
if (wrappingKey) await
|
|
4516
|
+
if (wrappingKey) await storage3.deleteShare(userId).catch(() => {
|
|
4509
4517
|
});
|
|
4510
4518
|
events.emit("wallet-unlocking");
|
|
4511
4519
|
return returningUserUnlock(userId, jwt);
|
|
@@ -4518,7 +4526,7 @@ async function createDecaneConnect(config) {
|
|
|
4518
4526
|
} catch (e) {
|
|
4519
4527
|
zeroize(shareSet.device);
|
|
4520
4528
|
zeroize(shareSet.recovery);
|
|
4521
|
-
if (wrappingKey) await
|
|
4529
|
+
if (wrappingKey) await storage3.deleteShare(userId).catch(() => {
|
|
4522
4530
|
});
|
|
4523
4531
|
throw e;
|
|
4524
4532
|
}
|
|
@@ -4594,7 +4602,7 @@ async function createDecaneConnect(config) {
|
|
|
4594
4602
|
async function returningUserUnlock(userId, jwt) {
|
|
4595
4603
|
const rawAddresses = await api.getAddresses(jwt);
|
|
4596
4604
|
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
|
|
4597
|
-
const stored = await
|
|
4605
|
+
const stored = await storage3.getShare(userId);
|
|
4598
4606
|
let deviceShare;
|
|
4599
4607
|
let joinedViaPasskeySync = false;
|
|
4600
4608
|
let migrating = false;
|
|
@@ -4646,7 +4654,7 @@ async function createDecaneConnect(config) {
|
|
|
4646
4654
|
rpId,
|
|
4647
4655
|
teeUrl,
|
|
4648
4656
|
api,
|
|
4649
|
-
storage:
|
|
4657
|
+
storage: storage3,
|
|
4650
4658
|
onProgress: config.onRecoveryProgress,
|
|
4651
4659
|
promptForRecoveryFile: config.promptForRecoveryFile,
|
|
4652
4660
|
onNewRecoveryShare: async (recoveryShare, shareSetVersion) => {
|
|
@@ -4660,7 +4668,7 @@ async function createDecaneConnect(config) {
|
|
|
4660
4668
|
});
|
|
4661
4669
|
deviceShare = recovered.deviceShare;
|
|
4662
4670
|
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
4663
|
-
if (!identityTier && !await
|
|
4671
|
+
if (!identityTier && !await storage3.getShare(userId) && isPasskeySupported()) {
|
|
4664
4672
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
4665
4673
|
}
|
|
4666
4674
|
} else {
|
|
@@ -4689,7 +4697,7 @@ async function createDecaneConnect(config) {
|
|
|
4689
4697
|
} catch (e) {
|
|
4690
4698
|
if (e instanceof StaleDeviceShareError) {
|
|
4691
4699
|
zeroize(deviceShare);
|
|
4692
|
-
await
|
|
4700
|
+
await storage3.deleteShare(userId);
|
|
4693
4701
|
if (stored) return returningUserUnlock(userId, jwt);
|
|
4694
4702
|
} else {
|
|
4695
4703
|
zeroize(deviceShare);
|
|
@@ -4717,7 +4725,7 @@ async function createDecaneConnect(config) {
|
|
|
4717
4725
|
const result = await tee.rotateShares(jwt);
|
|
4718
4726
|
try {
|
|
4719
4727
|
await persistRotatedDeviceShare(userId, result.deviceShare);
|
|
4720
|
-
const refreshed = await
|
|
4728
|
+
const refreshed = await storage3.getShare(userId);
|
|
4721
4729
|
if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
|
|
4722
4730
|
await api.putRecoveryBackup(jwt, {
|
|
4723
4731
|
wrappedDeviceShare: refreshed.wrappedDeviceShare,
|
|
@@ -4750,13 +4758,13 @@ async function createDecaneConnect(config) {
|
|
|
4750
4758
|
}
|
|
4751
4759
|
async function passkeyLoginCandidate() {
|
|
4752
4760
|
if (!persisted?.userId || !isPasskeySupported()) return null;
|
|
4753
|
-
return usablePasskeyShare(await
|
|
4761
|
+
return usablePasskeyShare(await storage3.getShare(persisted.userId));
|
|
4754
4762
|
}
|
|
4755
4763
|
async function passkeyDeviceCandidate() {
|
|
4756
4764
|
if (!isPasskeySupported()) return null;
|
|
4757
4765
|
if (persisted?.userId) return passkeyLoginCandidate();
|
|
4758
|
-
if (lastUserId) return usablePasskeyShare(await
|
|
4759
|
-
const candidates = (await
|
|
4766
|
+
if (lastUserId) return usablePasskeyShare(await storage3.getShare(lastUserId));
|
|
4767
|
+
const candidates = (await storage3.listShares()).map(usablePasskeyShare).filter(Boolean);
|
|
4760
4768
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4761
4769
|
}
|
|
4762
4770
|
async function canOfferPasskey() {
|
|
@@ -4764,15 +4772,15 @@ async function createDecaneConnect(config) {
|
|
|
4764
4772
|
if (await passkeyDeviceCandidate()) return true;
|
|
4765
4773
|
const known = persisted?.userId ?? lastUserId;
|
|
4766
4774
|
if (!known) return false;
|
|
4767
|
-
return await
|
|
4775
|
+
return await storage3.getShare(known) == null;
|
|
4768
4776
|
}
|
|
4769
4777
|
async function rememberProfile(userId, profile) {
|
|
4770
4778
|
const incoming = Object.fromEntries(Object.entries(profile).filter(([, v]) => v));
|
|
4771
4779
|
if (!Object.keys(incoming).length) return;
|
|
4772
4780
|
try {
|
|
4773
|
-
const stored = await
|
|
4781
|
+
const stored = await storage3.getShare(userId);
|
|
4774
4782
|
if (!stored) return;
|
|
4775
|
-
await
|
|
4783
|
+
await storage3.putShare({ ...stored, profile: { ...stored.profile, ...incoming } });
|
|
4776
4784
|
} catch {
|
|
4777
4785
|
}
|
|
4778
4786
|
}
|
|
@@ -4796,7 +4804,7 @@ async function createDecaneConnect(config) {
|
|
|
4796
4804
|
savePersisted(PERSIST_KEY, persisted);
|
|
4797
4805
|
events.emit("connected");
|
|
4798
4806
|
persistLiveSession();
|
|
4799
|
-
const stored = await
|
|
4807
|
+
const stored = await storage3.getShare(userId);
|
|
4800
4808
|
return {
|
|
4801
4809
|
addresses,
|
|
4802
4810
|
isNewUser: false,
|
|
@@ -4856,6 +4864,11 @@ async function createDecaneConnect(config) {
|
|
|
4856
4864
|
return new Promise(() => {
|
|
4857
4865
|
});
|
|
4858
4866
|
}
|
|
4867
|
+
async function finishKingsChatReturn(code) {
|
|
4868
|
+
clearKingsChatState();
|
|
4869
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
4870
|
+
return handleAuthResult(await api.authKingsChatCode(code));
|
|
4871
|
+
}
|
|
4859
4872
|
async function handleAuthResult(result) {
|
|
4860
4873
|
let addresses;
|
|
4861
4874
|
if (!result.hasShare) {
|
|
@@ -4918,6 +4931,8 @@ async function createDecaneConnect(config) {
|
|
|
4918
4931
|
* this never starts a flow.
|
|
4919
4932
|
*/
|
|
4920
4933
|
async completeRedirectSignIn() {
|
|
4934
|
+
const pending = readKingsChatReturn(window.location.search);
|
|
4935
|
+
if (pending) return finishKingsChatReturn(pending.code);
|
|
4921
4936
|
const outcome = readRedirectOutcome(window.location.search);
|
|
4922
4937
|
if (outcome.kind === "none") {
|
|
4923
4938
|
throw new CallSequenceError("No sign-in result in the URL to complete");
|
|
@@ -4936,12 +4951,12 @@ async function createDecaneConnect(config) {
|
|
|
4936
4951
|
return handleAuthResult(await api.authGoogleToken(idToken));
|
|
4937
4952
|
},
|
|
4938
4953
|
async connectWithKingsChat() {
|
|
4954
|
+
const pending = readKingsChatReturn(window.location.search);
|
|
4955
|
+
if (pending) return finishKingsChatReturn(pending.code);
|
|
4939
4956
|
const { clientId } = await api.getKingsChatConfig();
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
scopes: config.kingsChatScopes
|
|
4957
|
+
window.location.href = beginKingsChatLogin(clientId);
|
|
4958
|
+
return new Promise(() => {
|
|
4943
4959
|
});
|
|
4944
|
-
return client.connectWithKingsChatToken(tokens2.accessToken);
|
|
4945
4960
|
},
|
|
4946
4961
|
async connectWithKingsChatToken(accessToken) {
|
|
4947
4962
|
if (!accessToken) {
|
|
@@ -4955,6 +4970,20 @@ async function createDecaneConnect(config) {
|
|
|
4955
4970
|
const result = await api.authExternal(token, opts?.providerId);
|
|
4956
4971
|
return handleAuthResult(result);
|
|
4957
4972
|
},
|
|
4973
|
+
async linkWithToken(token, opts) {
|
|
4974
|
+
if (!token) throw new CallSequenceError("linkWithToken requires a token");
|
|
4975
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4976
|
+
const result = await api.linkExternal(auth.jwt, token, opts?.providerId);
|
|
4977
|
+
return result.id ? { alreadyLinked: result.alreadyLinked, id: result.id } : { alreadyLinked: result.alreadyLinked };
|
|
4978
|
+
},
|
|
4979
|
+
async listLinkedLogins() {
|
|
4980
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4981
|
+
return (await api.listLinkedLogins(auth.jwt)).logins;
|
|
4982
|
+
},
|
|
4983
|
+
async unlinkLogin(id) {
|
|
4984
|
+
if (!auth) throw new ReconnectRequiredError();
|
|
4985
|
+
await api.unlinkLogin(auth.jwt, id);
|
|
4986
|
+
},
|
|
4958
4987
|
async connectWithAccessToken(token) {
|
|
4959
4988
|
const claims = decodeAccessToken(token);
|
|
4960
4989
|
if (!claims) throw new InvalidAccessTokenError("not a Decane access token");
|
|
@@ -4969,7 +4998,7 @@ async function createDecaneConnect(config) {
|
|
|
4969
4998
|
events.emit("connected");
|
|
4970
4999
|
persistLiveSession();
|
|
4971
5000
|
scheduleAccessTokenExpiry(token, claims.expiresAt);
|
|
4972
|
-
const stored = await
|
|
5001
|
+
const stored = await storage3.getShare(claims.userId);
|
|
4973
5002
|
return {
|
|
4974
5003
|
addresses,
|
|
4975
5004
|
isNewUser: false,
|
|
@@ -5005,7 +5034,7 @@ async function createDecaneConnect(config) {
|
|
|
5005
5034
|
const candidate = await passkeyDeviceCandidate();
|
|
5006
5035
|
if (candidate) return passkeyLogin(candidate);
|
|
5007
5036
|
const known = persisted?.userId ?? lastUserId;
|
|
5008
|
-
if (isPasskeySupported() && known && await
|
|
5037
|
+
if (isPasskeySupported() && known && await storage3.getShare(known) == null) {
|
|
5009
5038
|
return passkeyLoginWithoutShare(known);
|
|
5010
5039
|
}
|
|
5011
5040
|
throw new NewDeviceError();
|
|
@@ -5024,7 +5053,7 @@ async function createDecaneConnect(config) {
|
|
|
5024
5053
|
async setUnlockPassword(password) {
|
|
5025
5054
|
if (!auth) throw new ReconnectRequiredError();
|
|
5026
5055
|
validateUnlockPassword(password);
|
|
5027
|
-
const stored = await
|
|
5056
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5028
5057
|
if (!stored) throw new NewDeviceError();
|
|
5029
5058
|
const deviceShare = await aesGcmDecrypt(
|
|
5030
5059
|
await getWrappingKey(stored),
|
|
@@ -5043,7 +5072,7 @@ async function createDecaneConnect(config) {
|
|
|
5043
5072
|
);
|
|
5044
5073
|
const iv = generateRandomBytes(12);
|
|
5045
5074
|
const wrappedDeviceShare = await aesGcmEncrypt(shareKey, deviceShare, iv);
|
|
5046
|
-
await
|
|
5075
|
+
await storage3.putShare({
|
|
5047
5076
|
...stored,
|
|
5048
5077
|
unlockMethod: "password",
|
|
5049
5078
|
wrappedDeviceShare,
|
|
@@ -5123,9 +5152,9 @@ async function createDecaneConnect(config) {
|
|
|
5123
5152
|
async removeUnlockPassword() {
|
|
5124
5153
|
if (!auth) throw new ReconnectRequiredError();
|
|
5125
5154
|
await api.revokeUnlockTokens(auth.jwt);
|
|
5126
|
-
const stored = await
|
|
5155
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5127
5156
|
if (!stored || stored.unlockMethod !== "password") return;
|
|
5128
|
-
await
|
|
5157
|
+
await storage3.putShare({
|
|
5129
5158
|
...stored,
|
|
5130
5159
|
wrappedUnlockToken: void 0,
|
|
5131
5160
|
unlockTokenIv: void 0
|
|
@@ -5133,7 +5162,7 @@ async function createDecaneConnect(config) {
|
|
|
5133
5162
|
},
|
|
5134
5163
|
async hasPasskey() {
|
|
5135
5164
|
if (!auth) return false;
|
|
5136
|
-
const stored = await
|
|
5165
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5137
5166
|
return stored?.unlockMethod === "passkey";
|
|
5138
5167
|
},
|
|
5139
5168
|
async addPasskey() {
|
|
@@ -5144,7 +5173,7 @@ async function createDecaneConnect(config) {
|
|
|
5144
5173
|
}
|
|
5145
5174
|
if (!auth) throw new ReconnectRequiredError();
|
|
5146
5175
|
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
5147
|
-
const stored = await
|
|
5176
|
+
const stored = await storage3.getShare(auth.userId);
|
|
5148
5177
|
if (stored?.unlockMethod === "passkey") return;
|
|
5149
5178
|
if (!stored) {
|
|
5150
5179
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
@@ -5177,12 +5206,12 @@ async function createDecaneConnect(config) {
|
|
|
5177
5206
|
},
|
|
5178
5207
|
async deviceProtected() {
|
|
5179
5208
|
if (!auth) return false;
|
|
5180
|
-
return await
|
|
5209
|
+
return await storage3.getShare(auth.userId) !== null;
|
|
5181
5210
|
},
|
|
5182
5211
|
async protectDevice() {
|
|
5183
5212
|
if (identityTier) return false;
|
|
5184
5213
|
if (!auth) throw new ReconnectRequiredError();
|
|
5185
|
-
if (await
|
|
5214
|
+
if (await storage3.getShare(auth.userId)) return true;
|
|
5186
5215
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
5187
5216
|
const tee = new TeeClient(teeUrl);
|
|
5188
5217
|
const provisioned = await tee.provisionDeviceShare(auth.jwt);
|
|
@@ -5192,7 +5221,7 @@ async function createDecaneConnect(config) {
|
|
|
5192
5221
|
} finally {
|
|
5193
5222
|
zeroize(provisioned);
|
|
5194
5223
|
}
|
|
5195
|
-
return await
|
|
5224
|
+
return await storage3.getShare(auth.userId) !== null;
|
|
5196
5225
|
},
|
|
5197
5226
|
async unlock() {
|
|
5198
5227
|
if (!auth) {
|
|
@@ -6155,7 +6184,6 @@ function SignInView({
|
|
|
6155
6184
|
await onKingsChatSignIn();
|
|
6156
6185
|
} catch (e) {
|
|
6157
6186
|
setError(e.message);
|
|
6158
|
-
} finally {
|
|
6159
6187
|
setKingsChatLoading(false);
|
|
6160
6188
|
}
|
|
6161
6189
|
}
|
|
@@ -6291,7 +6319,7 @@ function SignInView({
|
|
|
6291
6319
|
value: phone,
|
|
6292
6320
|
onChange: (e) => setPhone(e.target.value)
|
|
6293
6321
|
}
|
|
6294
|
-
), /* @__PURE__ */ import_react2.default.createElement("button", { type: "submit", className: "dcm-submit", disabled: busy || !phone.trim() }, phoneLoading ? "Sending\u2026" : "Submit")), authMethods.includes("x") && onXSignIn && /* @__PURE__ */ import_react2.default.createElement("button", { type: "button", className: "dcm-method", onClick: handleX, disabled: busy }, /* @__PURE__ */ import_react2.default.createElement(XLogo, { size: 16 }), /* @__PURE__ */ import_react2.default.createElement("span", null, xLoading ? "Redirecting to X\u2026" : "X")), authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ import_react2.default.createElement("button", { type: "button", className: "dcm-method", onClick: handleKingsChat, disabled: busy }, /* @__PURE__ */ import_react2.default.createElement(ChatIcon, { size: 18 }), /* @__PURE__ */ import_react2.default.createElement("span", null, kingsChatLoading ? "
|
|
6322
|
+
), /* @__PURE__ */ import_react2.default.createElement("button", { type: "submit", className: "dcm-submit", disabled: busy || !phone.trim() }, phoneLoading ? "Sending\u2026" : "Submit")), authMethods.includes("x") && onXSignIn && /* @__PURE__ */ import_react2.default.createElement("button", { type: "button", className: "dcm-method", onClick: handleX, disabled: busy }, /* @__PURE__ */ import_react2.default.createElement(XLogo, { size: 16 }), /* @__PURE__ */ import_react2.default.createElement("span", null, xLoading ? "Redirecting to X\u2026" : "X")), authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ import_react2.default.createElement("button", { type: "button", className: "dcm-method", onClick: handleKingsChat, disabled: busy }, /* @__PURE__ */ import_react2.default.createElement(ChatIcon, { size: 18 }), /* @__PURE__ */ import_react2.default.createElement("span", null, kingsChatLoading ? "Redirecting to KingsChat\u2026" : "KingsChat")), customAuthLabel && onCustomAuthSignIn && /* @__PURE__ */ import_react2.default.createElement("button", { type: "button", className: "dcm-method", onClick: handleCustomAuth, disabled: busy }, /* @__PURE__ */ import_react2.default.createElement(UserIcon, { size: 18 }), /* @__PURE__ */ import_react2.default.createElement("span", null, customLoading ? "Connecting\u2026" : customAuthLabel))), extra, errorLine, canUsePasskey && onPasskeySignIn && /* @__PURE__ */ import_react2.default.createElement("button", { type: "button", className: "dcm-link", onClick: () => {
|
|
6295
6323
|
setError("");
|
|
6296
6324
|
setShowOtherMethods(false);
|
|
6297
6325
|
} }, "Use my passkey instead"), /* @__PURE__ */ import_react2.default.createElement(ProtectedBy, null));
|
|
@@ -6948,7 +6976,7 @@ function DecaneKit({ config, children }) {
|
|
|
6948
6976
|
sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
|
|
6949
6977
|
});
|
|
6950
6978
|
const params = new URLSearchParams(window.location.search);
|
|
6951
|
-
if (params.has("decane_jwt") || params.has("decane_error")) {
|
|
6979
|
+
if (params.has("decane_jwt") || params.has("decane_error") || readKingsChatReturn(window.location.search)) {
|
|
6952
6980
|
sdk.completeRedirectSignIn().then(applyResult).catch((e) => {
|
|
6953
6981
|
setSocialAuth((p) => p.phase ? { ...p, phase: null } : p);
|
|
6954
6982
|
console.error(e);
|
|
@@ -7036,6 +7064,21 @@ function DecaneKit({ config, children }) {
|
|
|
7036
7064
|
throw e;
|
|
7037
7065
|
}
|
|
7038
7066
|
}, [clearPhase]);
|
|
7067
|
+
const linkWithToken = (0, import_react5.useCallback)(async (token, opts) => {
|
|
7068
|
+
const sdk = socialRef.current;
|
|
7069
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
7070
|
+
return sdk.linkWithToken(token, opts);
|
|
7071
|
+
}, []);
|
|
7072
|
+
const listLinkedLogins = (0, import_react5.useCallback)(async () => {
|
|
7073
|
+
const sdk = socialRef.current;
|
|
7074
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
7075
|
+
return sdk.listLinkedLogins();
|
|
7076
|
+
}, []);
|
|
7077
|
+
const unlinkLogin = (0, import_react5.useCallback)(async (id) => {
|
|
7078
|
+
const sdk = socialRef.current;
|
|
7079
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
7080
|
+
await sdk.unlinkLogin(id);
|
|
7081
|
+
}, []);
|
|
7039
7082
|
const connectWithToken = (0, import_react5.useCallback)(async (token, opts) => {
|
|
7040
7083
|
const sdk = socialRef.current;
|
|
7041
7084
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
@@ -7253,6 +7296,9 @@ function DecaneKit({ config, children }) {
|
|
|
7253
7296
|
verifyPhoneCode: verifyPhoneCode2,
|
|
7254
7297
|
connectWithToken,
|
|
7255
7298
|
connectWithAccessToken,
|
|
7299
|
+
linkWithToken,
|
|
7300
|
+
listLinkedLogins,
|
|
7301
|
+
unlinkLogin,
|
|
7256
7302
|
customAuthLabel: customAuthConfig?.label,
|
|
7257
7303
|
connectWithCustomAuth: customAuthConfig ? connectWithCustomAuth : void 0,
|
|
7258
7304
|
socialUnlock,
|
|
@@ -7369,6 +7415,9 @@ function useSocialAuth() {
|
|
|
7369
7415
|
verifyPhoneCode: verifyPhoneCode2,
|
|
7370
7416
|
connectWithToken,
|
|
7371
7417
|
connectWithAccessToken,
|
|
7418
|
+
linkWithToken,
|
|
7419
|
+
listLinkedLogins,
|
|
7420
|
+
unlinkLogin,
|
|
7372
7421
|
customAuthLabel,
|
|
7373
7422
|
connectWithCustomAuth,
|
|
7374
7423
|
signInWithPasskey: ctxSignInWithPasskey,
|
|
@@ -7572,6 +7621,11 @@ function useSocialAuth() {
|
|
|
7572
7621
|
confirmPhoneCode,
|
|
7573
7622
|
signInWithToken,
|
|
7574
7623
|
signInWithAccessToken,
|
|
7624
|
+
// Linked logins: while signed in, attach a login from your own auth (a
|
|
7625
|
+
// phone number your backend verified, say) so it opens this same wallet.
|
|
7626
|
+
linkWithToken,
|
|
7627
|
+
listLinkedLogins,
|
|
7628
|
+
unlinkLogin,
|
|
7575
7629
|
signInWithCustomAuth: connectWithCustomAuth ? signInWithCustomAuth : void 0,
|
|
7576
7630
|
signInWithPasskey,
|
|
7577
7631
|
unlock,
|
|
@@ -8126,6 +8180,7 @@ function useSignTypedData() {
|
|
|
8126
8180
|
InvalidApiKeyError,
|
|
8127
8181
|
InvalidPinError,
|
|
8128
8182
|
InvalidRecoveryFileError,
|
|
8183
|
+
LoginInUseError,
|
|
8129
8184
|
MIN_PASSWORD_LENGTH,
|
|
8130
8185
|
NetworkError,
|
|
8131
8186
|
NewDeviceError,
|