decane-connect-kit 2.20.0 → 2.22.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 +71 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +71 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -229,6 +229,8 @@ interface ConnectResult {
|
|
|
229
229
|
isNewUser: boolean;
|
|
230
230
|
/** Present when the provider released a profile — see {@link AuthProfile}. */
|
|
231
231
|
profile?: AuthProfile;
|
|
232
|
+
/** See {@link AuthResult.providerSubject}. */
|
|
233
|
+
providerSubject?: string;
|
|
232
234
|
}
|
|
233
235
|
/** A recorded on-chain transaction in the user's history. */
|
|
234
236
|
interface TransactionRecord {
|
|
@@ -396,6 +398,14 @@ interface DecaneConnect {
|
|
|
396
398
|
setUnlockPassword(password: string): Promise<void>;
|
|
397
399
|
/** True when this device can open a session from a password alone. */
|
|
398
400
|
canUnlockWithPassword(): Promise<boolean>;
|
|
401
|
+
/**
|
|
402
|
+
* "Use a different account." disconnect() keeps one fact about the account
|
|
403
|
+
* that signed out — its userId — so canSignInWithPasskey() and
|
|
404
|
+
* canUnlockWithPassword() can offer that account's own way back in even
|
|
405
|
+
* when several accounts are enrolled on this browser. This drops it. The
|
|
406
|
+
* device shares themselves are untouched.
|
|
407
|
+
*/
|
|
408
|
+
forgetLastUser(): void;
|
|
399
409
|
/** Open a session from the password alone — no provider, no passkey. */
|
|
400
410
|
unlockWithPassword(password: string): Promise<ConnectResult>;
|
|
401
411
|
/** Turn the password path off, revoking the token account-wide. */
|
|
@@ -547,6 +557,16 @@ interface AuthProfile {
|
|
|
547
557
|
name?: string;
|
|
548
558
|
email?: string;
|
|
549
559
|
picture?: string;
|
|
560
|
+
/**
|
|
561
|
+
* The handle the user signs in under, where the provider has one. X sets it
|
|
562
|
+
* and releases no email at all, so for those accounts this is the only
|
|
563
|
+
* human-readable thing there is.
|
|
564
|
+
*
|
|
565
|
+
* Display only. A handle can be released by its owner and registered by
|
|
566
|
+
* somebody else, so never key an account, a lookup or access on it — use
|
|
567
|
+
* {@link AuthResult.providerSubject}, which cannot move.
|
|
568
|
+
*/
|
|
569
|
+
username?: string;
|
|
550
570
|
}
|
|
551
571
|
interface AuthResult {
|
|
552
572
|
jwt: string;
|
|
@@ -554,6 +574,15 @@ interface AuthResult {
|
|
|
554
574
|
hasShare: boolean;
|
|
555
575
|
userId: string;
|
|
556
576
|
profile?: AuthProfile;
|
|
577
|
+
/**
|
|
578
|
+
* The provider's own stable id for this user — X's numeric user id, for
|
|
579
|
+
* instance. Present only where the provider gives one.
|
|
580
|
+
*
|
|
581
|
+
* Unlike a handle or an email this cannot be reassigned, which is what makes
|
|
582
|
+
* it safe to match an account against: the same value identifies the same
|
|
583
|
+
* person on both sides of a migration.
|
|
584
|
+
*/
|
|
585
|
+
providerSubject?: string;
|
|
557
586
|
}
|
|
558
587
|
|
|
559
588
|
declare class ApiClient {
|
|
@@ -1229,6 +1258,15 @@ interface SocialAuthState {
|
|
|
1229
1258
|
* you need it in that state.
|
|
1230
1259
|
*/
|
|
1231
1260
|
profile: AuthProfile | null;
|
|
1261
|
+
/**
|
|
1262
|
+
* The provider's own stable id for this user, where it gives one — X's
|
|
1263
|
+
* numeric user id, for instance. Null for providers that do not.
|
|
1264
|
+
*
|
|
1265
|
+
* Unlike a handle or an email this cannot be reassigned, so it is the value
|
|
1266
|
+
* to match an account against. Like `profile` it comes from the sign-in and
|
|
1267
|
+
* cannot be re-fetched, so copy it if you need it after a reload.
|
|
1268
|
+
*/
|
|
1269
|
+
providerSubject: string | null;
|
|
1232
1270
|
isUnlocked: boolean;
|
|
1233
1271
|
sessionExpiresAt: number | null;
|
|
1234
1272
|
/**
|
|
@@ -1322,6 +1360,7 @@ declare function useSocialAuth(): {
|
|
|
1322
1360
|
sessionExpiresAt: number | null;
|
|
1323
1361
|
canUsePasskey: boolean;
|
|
1324
1362
|
profile: AuthProfile | null;
|
|
1363
|
+
providerSubject: string | null;
|
|
1325
1364
|
phase: "creating" | "unlocking" | null;
|
|
1326
1365
|
isCreatingWallet: boolean;
|
|
1327
1366
|
needsReconnect: boolean;
|
|
@@ -1413,6 +1452,7 @@ declare function useSocialWallet(): {
|
|
|
1413
1452
|
canUnlockWithPassword: () => Promise<boolean>;
|
|
1414
1453
|
unlockWithPassword: (password: string) => Promise<void>;
|
|
1415
1454
|
removeUnlockPassword: () => Promise<void>;
|
|
1455
|
+
forgetLastUser: () => void;
|
|
1416
1456
|
rotateShares: (opts: {
|
|
1417
1457
|
password: string;
|
|
1418
1458
|
passwordHint?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -229,6 +229,8 @@ interface ConnectResult {
|
|
|
229
229
|
isNewUser: boolean;
|
|
230
230
|
/** Present when the provider released a profile — see {@link AuthProfile}. */
|
|
231
231
|
profile?: AuthProfile;
|
|
232
|
+
/** See {@link AuthResult.providerSubject}. */
|
|
233
|
+
providerSubject?: string;
|
|
232
234
|
}
|
|
233
235
|
/** A recorded on-chain transaction in the user's history. */
|
|
234
236
|
interface TransactionRecord {
|
|
@@ -396,6 +398,14 @@ interface DecaneConnect {
|
|
|
396
398
|
setUnlockPassword(password: string): Promise<void>;
|
|
397
399
|
/** True when this device can open a session from a password alone. */
|
|
398
400
|
canUnlockWithPassword(): Promise<boolean>;
|
|
401
|
+
/**
|
|
402
|
+
* "Use a different account." disconnect() keeps one fact about the account
|
|
403
|
+
* that signed out — its userId — so canSignInWithPasskey() and
|
|
404
|
+
* canUnlockWithPassword() can offer that account's own way back in even
|
|
405
|
+
* when several accounts are enrolled on this browser. This drops it. The
|
|
406
|
+
* device shares themselves are untouched.
|
|
407
|
+
*/
|
|
408
|
+
forgetLastUser(): void;
|
|
399
409
|
/** Open a session from the password alone — no provider, no passkey. */
|
|
400
410
|
unlockWithPassword(password: string): Promise<ConnectResult>;
|
|
401
411
|
/** Turn the password path off, revoking the token account-wide. */
|
|
@@ -547,6 +557,16 @@ interface AuthProfile {
|
|
|
547
557
|
name?: string;
|
|
548
558
|
email?: string;
|
|
549
559
|
picture?: string;
|
|
560
|
+
/**
|
|
561
|
+
* The handle the user signs in under, where the provider has one. X sets it
|
|
562
|
+
* and releases no email at all, so for those accounts this is the only
|
|
563
|
+
* human-readable thing there is.
|
|
564
|
+
*
|
|
565
|
+
* Display only. A handle can be released by its owner and registered by
|
|
566
|
+
* somebody else, so never key an account, a lookup or access on it — use
|
|
567
|
+
* {@link AuthResult.providerSubject}, which cannot move.
|
|
568
|
+
*/
|
|
569
|
+
username?: string;
|
|
550
570
|
}
|
|
551
571
|
interface AuthResult {
|
|
552
572
|
jwt: string;
|
|
@@ -554,6 +574,15 @@ interface AuthResult {
|
|
|
554
574
|
hasShare: boolean;
|
|
555
575
|
userId: string;
|
|
556
576
|
profile?: AuthProfile;
|
|
577
|
+
/**
|
|
578
|
+
* The provider's own stable id for this user — X's numeric user id, for
|
|
579
|
+
* instance. Present only where the provider gives one.
|
|
580
|
+
*
|
|
581
|
+
* Unlike a handle or an email this cannot be reassigned, which is what makes
|
|
582
|
+
* it safe to match an account against: the same value identifies the same
|
|
583
|
+
* person on both sides of a migration.
|
|
584
|
+
*/
|
|
585
|
+
providerSubject?: string;
|
|
557
586
|
}
|
|
558
587
|
|
|
559
588
|
declare class ApiClient {
|
|
@@ -1229,6 +1258,15 @@ interface SocialAuthState {
|
|
|
1229
1258
|
* you need it in that state.
|
|
1230
1259
|
*/
|
|
1231
1260
|
profile: AuthProfile | null;
|
|
1261
|
+
/**
|
|
1262
|
+
* The provider's own stable id for this user, where it gives one — X's
|
|
1263
|
+
* numeric user id, for instance. Null for providers that do not.
|
|
1264
|
+
*
|
|
1265
|
+
* Unlike a handle or an email this cannot be reassigned, so it is the value
|
|
1266
|
+
* to match an account against. Like `profile` it comes from the sign-in and
|
|
1267
|
+
* cannot be re-fetched, so copy it if you need it after a reload.
|
|
1268
|
+
*/
|
|
1269
|
+
providerSubject: string | null;
|
|
1232
1270
|
isUnlocked: boolean;
|
|
1233
1271
|
sessionExpiresAt: number | null;
|
|
1234
1272
|
/**
|
|
@@ -1322,6 +1360,7 @@ declare function useSocialAuth(): {
|
|
|
1322
1360
|
sessionExpiresAt: number | null;
|
|
1323
1361
|
canUsePasskey: boolean;
|
|
1324
1362
|
profile: AuthProfile | null;
|
|
1363
|
+
providerSubject: string | null;
|
|
1325
1364
|
phase: "creating" | "unlocking" | null;
|
|
1326
1365
|
isCreatingWallet: boolean;
|
|
1327
1366
|
needsReconnect: boolean;
|
|
@@ -1413,6 +1452,7 @@ declare function useSocialWallet(): {
|
|
|
1413
1452
|
canUnlockWithPassword: () => Promise<boolean>;
|
|
1414
1453
|
unlockWithPassword: (password: string) => Promise<void>;
|
|
1415
1454
|
removeUnlockPassword: () => Promise<void>;
|
|
1455
|
+
forgetLastUser: () => void;
|
|
1416
1456
|
rotateShares: (opts: {
|
|
1417
1457
|
password: string;
|
|
1418
1458
|
passwordHint?: string;
|
package/dist/index.js
CHANGED
|
@@ -1956,10 +1956,13 @@ function readRedirectOutcome(search) {
|
|
|
1956
1956
|
const name = params.get("decane_name");
|
|
1957
1957
|
const email = params.get("decane_email");
|
|
1958
1958
|
const picture = params.get("decane_picture");
|
|
1959
|
+
const username = params.get("decane_username");
|
|
1960
|
+
const providerSubject = params.get("decane_provider_subject");
|
|
1959
1961
|
const profile = {
|
|
1960
1962
|
...name ? { name } : {},
|
|
1961
1963
|
...email ? { email } : {},
|
|
1962
|
-
...picture ? { picture } : {}
|
|
1964
|
+
...picture ? { picture } : {},
|
|
1965
|
+
...username ? { username } : {}
|
|
1963
1966
|
};
|
|
1964
1967
|
return {
|
|
1965
1968
|
kind: "result",
|
|
@@ -1972,7 +1975,8 @@ function readRedirectOutcome(search) {
|
|
|
1972
1975
|
// test `result.profile` rather than an object that is always present but
|
|
1973
1976
|
// sometimes empty. X never sends an email, so this is the normal case
|
|
1974
1977
|
// there rather than an edge one.
|
|
1975
|
-
...Object.keys(profile).length ? { profile } : {}
|
|
1978
|
+
...Object.keys(profile).length ? { profile } : {},
|
|
1979
|
+
...providerSubject ? { providerSubject } : {}
|
|
1976
1980
|
}
|
|
1977
1981
|
};
|
|
1978
1982
|
}
|
|
@@ -3603,6 +3607,29 @@ function clearPersisted(key) {
|
|
|
3603
3607
|
} catch {
|
|
3604
3608
|
}
|
|
3605
3609
|
}
|
|
3610
|
+
function makeLastUserKey(apiKey) {
|
|
3611
|
+
return `${makePersistKey(apiKey)}:last-user`;
|
|
3612
|
+
}
|
|
3613
|
+
function loadLastUser(key) {
|
|
3614
|
+
try {
|
|
3615
|
+
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
|
|
3616
|
+
return raw ? JSON.parse(raw).userId ?? null : null;
|
|
3617
|
+
} catch {
|
|
3618
|
+
return null;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
function saveLastUser(key, userId) {
|
|
3622
|
+
try {
|
|
3623
|
+
localStorage.setItem(key, JSON.stringify({ userId }));
|
|
3624
|
+
} catch {
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
function clearLastUser(key) {
|
|
3628
|
+
try {
|
|
3629
|
+
localStorage.removeItem(key);
|
|
3630
|
+
} catch {
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3606
3633
|
function sessionPersistKey(apiKey) {
|
|
3607
3634
|
return `decane:sess:${apiKey.slice(0, 20)}`;
|
|
3608
3635
|
}
|
|
@@ -3641,6 +3668,7 @@ async function createDecaneConnect(config) {
|
|
|
3641
3668
|
const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
|
|
3642
3669
|
const rpName = config.rpName ?? "Decane Connect";
|
|
3643
3670
|
const PERSIST_KEY = makePersistKey(config.apiKey);
|
|
3671
|
+
const LAST_USER_KEY = makeLastUserKey(config.apiKey);
|
|
3644
3672
|
const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
|
|
3645
3673
|
const events = new EventEmitter();
|
|
3646
3674
|
const api = new ApiClient(baseUrl, config.apiKey, config.appId);
|
|
@@ -3653,6 +3681,7 @@ async function createDecaneConnect(config) {
|
|
|
3653
3681
|
);
|
|
3654
3682
|
let auth = null;
|
|
3655
3683
|
let persisted = loadPersisted(PERSIST_KEY);
|
|
3684
|
+
let lastUserId = loadLastUser(LAST_USER_KEY);
|
|
3656
3685
|
let ethProvider = null;
|
|
3657
3686
|
const persistSession = config.persistSession !== false;
|
|
3658
3687
|
const serverAssistedRecovery = true;
|
|
@@ -3798,6 +3827,10 @@ async function createDecaneConnect(config) {
|
|
|
3798
3827
|
const stored = await storage.getShare(persisted.userId);
|
|
3799
3828
|
return hasPasswordUnlock(stored) ? stored : null;
|
|
3800
3829
|
}
|
|
3830
|
+
if (lastUserId) {
|
|
3831
|
+
const stored = await storage.getShare(lastUserId);
|
|
3832
|
+
return hasPasswordUnlock(stored) ? stored : null;
|
|
3833
|
+
}
|
|
3801
3834
|
const candidates = (await storage.listShares()).filter(hasPasswordUnlock);
|
|
3802
3835
|
return candidates.length === 1 ? candidates[0] : null;
|
|
3803
3836
|
}
|
|
@@ -4251,14 +4284,16 @@ async function createDecaneConnect(config) {
|
|
|
4251
4284
|
async function passkeyDeviceCandidate() {
|
|
4252
4285
|
if (!isPasskeySupported()) return null;
|
|
4253
4286
|
if (persisted?.userId) return passkeyLoginCandidate();
|
|
4287
|
+
if (lastUserId) return usablePasskeyShare(await storage.getShare(lastUserId));
|
|
4254
4288
|
const candidates = (await storage.listShares()).map(usablePasskeyShare).filter(Boolean);
|
|
4255
4289
|
return candidates.length === 1 ? candidates[0] : null;
|
|
4256
4290
|
}
|
|
4257
4291
|
async function canOfferPasskey() {
|
|
4258
4292
|
if (!isPasskeySupported()) return false;
|
|
4259
4293
|
if (await passkeyDeviceCandidate()) return true;
|
|
4260
|
-
|
|
4261
|
-
|
|
4294
|
+
const known = persisted?.userId ?? lastUserId;
|
|
4295
|
+
if (!known) return false;
|
|
4296
|
+
return await storage.getShare(known) == null;
|
|
4262
4297
|
}
|
|
4263
4298
|
async function rememberProfile(userId, profile) {
|
|
4264
4299
|
const incoming = Object.fromEntries(Object.entries(profile).filter(([, v]) => v));
|
|
@@ -4368,7 +4403,8 @@ async function createDecaneConnect(config) {
|
|
|
4368
4403
|
return {
|
|
4369
4404
|
addresses,
|
|
4370
4405
|
isNewUser: result.isNewUser,
|
|
4371
|
-
...result.profile ? { profile: result.profile } : {}
|
|
4406
|
+
...result.profile ? { profile: result.profile } : {},
|
|
4407
|
+
...result.providerSubject ? { providerSubject: result.providerSubject } : {}
|
|
4372
4408
|
};
|
|
4373
4409
|
}
|
|
4374
4410
|
const client = {
|
|
@@ -4470,8 +4506,9 @@ async function createDecaneConnect(config) {
|
|
|
4470
4506
|
async signInWithPasskey() {
|
|
4471
4507
|
const candidate = await passkeyDeviceCandidate();
|
|
4472
4508
|
if (candidate) return passkeyLogin(candidate);
|
|
4473
|
-
|
|
4474
|
-
|
|
4509
|
+
const known = persisted?.userId ?? lastUserId;
|
|
4510
|
+
if (isPasskeySupported() && known && await storage.getShare(known) == null) {
|
|
4511
|
+
return passkeyLoginWithoutShare(known);
|
|
4475
4512
|
}
|
|
4476
4513
|
throw new NewDeviceError();
|
|
4477
4514
|
},
|
|
@@ -4811,12 +4848,21 @@ async function createDecaneConnect(config) {
|
|
|
4811
4848
|
await api.revoke(auth.jwt).catch(() => {
|
|
4812
4849
|
});
|
|
4813
4850
|
}
|
|
4851
|
+
const leaving = auth?.userId ?? persisted?.userId ?? null;
|
|
4852
|
+
if (leaving) {
|
|
4853
|
+
lastUserId = leaving;
|
|
4854
|
+
saveLastUser(LAST_USER_KEY, leaving);
|
|
4855
|
+
}
|
|
4814
4856
|
auth = null;
|
|
4815
4857
|
persisted = null;
|
|
4816
4858
|
clearPersisted(PERSIST_KEY);
|
|
4817
4859
|
dropLiveSession();
|
|
4818
4860
|
events.emit("disconnected");
|
|
4819
4861
|
},
|
|
4862
|
+
forgetLastUser() {
|
|
4863
|
+
lastUserId = null;
|
|
4864
|
+
clearLastUser(LAST_USER_KEY);
|
|
4865
|
+
},
|
|
4820
4866
|
getAddresses() {
|
|
4821
4867
|
return auth?.addresses ?? persisted?.addresses ?? null;
|
|
4822
4868
|
},
|
|
@@ -7188,6 +7234,7 @@ var DEFAULT_SOCIAL = {
|
|
|
7188
7234
|
addresses: null,
|
|
7189
7235
|
isNewUser: false,
|
|
7190
7236
|
profile: null,
|
|
7237
|
+
providerSubject: null,
|
|
7191
7238
|
isUnlocked: false,
|
|
7192
7239
|
sessionExpiresAt: null,
|
|
7193
7240
|
needsReconnect: false,
|
|
@@ -7323,6 +7370,7 @@ function DecaneKit({ config, children }) {
|
|
|
7323
7370
|
addresses: result.addresses,
|
|
7324
7371
|
isNewUser: result.isNewUser,
|
|
7325
7372
|
profile: result.profile ?? null,
|
|
7373
|
+
providerSubject: result.providerSubject ?? null,
|
|
7326
7374
|
isUnlocked: sdk?.isUnlocked() ?? false,
|
|
7327
7375
|
sessionExpiresAt: sdk?.sessionExpiresAt() ?? null,
|
|
7328
7376
|
needsReconnect: false,
|
|
@@ -7529,6 +7577,14 @@ function DecaneKit({ config, children }) {
|
|
|
7529
7577
|
if (!sdk) return false;
|
|
7530
7578
|
return sdk.canUnlockWithPassword();
|
|
7531
7579
|
}, []);
|
|
7580
|
+
const socialForgetLastUser = useCallback3(() => {
|
|
7581
|
+
socialRef.current?.forgetLastUser();
|
|
7582
|
+
const sdk = socialRef.current;
|
|
7583
|
+
if (sdk) {
|
|
7584
|
+
sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
|
|
7585
|
+
});
|
|
7586
|
+
}
|
|
7587
|
+
}, []);
|
|
7532
7588
|
const socialUnlockWithPassword = useCallback3(async (password) => {
|
|
7533
7589
|
const sdk = socialRef.current;
|
|
7534
7590
|
if (!sdk) throw new Error("Social wallet not connected");
|
|
@@ -7605,6 +7661,7 @@ function DecaneKit({ config, children }) {
|
|
|
7605
7661
|
socialAddPasskey,
|
|
7606
7662
|
socialSetUnlockPassword,
|
|
7607
7663
|
socialCanUnlockWithPassword,
|
|
7664
|
+
socialForgetLastUser,
|
|
7608
7665
|
socialUnlockWithPassword,
|
|
7609
7666
|
socialRemoveUnlockPassword,
|
|
7610
7667
|
socialShouldPromptRecovery,
|
|
@@ -7855,6 +7912,9 @@ function useSocialAuth() {
|
|
|
7855
7912
|
// user first signed in, so it carries the same name and email a Google
|
|
7856
7913
|
// sign-in does rather than coming back anonymous.
|
|
7857
7914
|
profile: socialAuth.profile,
|
|
7915
|
+
// The provider's stable id, where it gives one. Null otherwise. Safe to
|
|
7916
|
+
// match an account on, unlike the handle in `profile.username`.
|
|
7917
|
+
providerSubject: socialAuth.providerSubject,
|
|
7858
7918
|
// Long-running work in progress, for driving your own status UI.
|
|
7859
7919
|
phase: socialAuth.phase,
|
|
7860
7920
|
isCreatingWallet: socialAuth.phase === "creating",
|
|
@@ -7911,6 +7971,7 @@ function useSocialWallet() {
|
|
|
7911
7971
|
socialAddPasskey,
|
|
7912
7972
|
socialSetUnlockPassword,
|
|
7913
7973
|
socialCanUnlockWithPassword,
|
|
7974
|
+
socialForgetLastUser,
|
|
7914
7975
|
socialUnlockWithPassword,
|
|
7915
7976
|
socialRemoveUnlockPassword,
|
|
7916
7977
|
socialShouldPromptRecovery,
|
|
@@ -8019,6 +8080,7 @@ function useSocialWallet() {
|
|
|
8019
8080
|
throw e;
|
|
8020
8081
|
}
|
|
8021
8082
|
}, [socialSetUnlockPassword]);
|
|
8083
|
+
const forgetLastUser = useCallback4(() => socialForgetLastUser(), [socialForgetLastUser]);
|
|
8022
8084
|
const canUnlockWithPassword = useCallback4(
|
|
8023
8085
|
() => socialCanUnlockWithPassword(),
|
|
8024
8086
|
[socialCanUnlockWithPassword]
|
|
@@ -8104,6 +8166,8 @@ function useSocialWallet() {
|
|
|
8104
8166
|
canUnlockWithPassword,
|
|
8105
8167
|
unlockWithPassword,
|
|
8106
8168
|
removeUnlockPassword,
|
|
8169
|
+
// "Use a different account": stop offering the account that signed out.
|
|
8170
|
+
forgetLastUser,
|
|
8107
8171
|
// Recovery (2-of-3 Shamir share set)
|
|
8108
8172
|
rotateShares,
|
|
8109
8173
|
exportPortableBackup,
|