Skip to content

Commit

Permalink
graceful upgrade from localstorage to idb without breaking sessions (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
krpeacock committed Aug 11, 2022
1 parent f79ae86 commit b550f81
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/generated/changelog.html
Expand Up @@ -19,6 +19,7 @@ <h2>Version 0.12.3</h2>
<ul>
<li>Also offers a generic Indexed Db keyval store, IdbKeyVal</li>
</ul>
<li>AuthClient migrates gracefully from localstorage to IDB when upgrading</li>
</ul>
<h2>Version 0.12.2</h2>
<ul>
Expand Down
63 changes: 62 additions & 1 deletion packages/auth-client/src/index.test.ts
Expand Up @@ -5,7 +5,12 @@ import { IDL } from '@dfinity/candid';
import { Ed25519KeyIdentity } from '@dfinity/identity';
import { Principal } from '@dfinity/principal';
import { AuthClient, ERROR_USER_INTERRUPT, IdbStorage } from './index';
import { AuthClientStorage } from './storage';
import {
AuthClientStorage,
KEY_STORAGE_DELEGATION,
KEY_STORAGE_KEY,
LocalStorage,
} from './storage';

/**
* A class for mocking the IDP service.
Expand Down Expand Up @@ -578,3 +583,59 @@ describe('Auth Client login', () => {
expect(idpWindow.close).toBeCalled();
});
});

describe('Migration from localstorage', () => {
it('should proceed normally if no values are stored in localstorage', async () => {
const storage: AuthClientStorage = {
remove: jest.fn(),
get: jest.fn(),
set: jest.fn(),
};

await AuthClient.create({ storage });

expect(storage.set as jest.Mock).toBeCalledTimes(0);
});
it('should not attempt to migrate if a delegation is already stored', async () => {
const storage: AuthClientStorage = {
remove: jest.fn(),
get: jest.fn(async x => {
if (x === KEY_STORAGE_DELEGATION) return 'test';
if (x === KEY_STORAGE_KEY) return 'key';
return null;
}),
set: jest.fn(),
};

await AuthClient.create({ storage });

expect(storage.set as jest.Mock).toBeCalledTimes(0);
});
it('should migrate storage from localstorage', async () => {
const localStorage = new LocalStorage('ic');
const storage: AuthClientStorage = {
remove: jest.fn(),
get: jest.fn(),
set: jest.fn(),
};

await localStorage.set(KEY_STORAGE_DELEGATION, 'test');
await localStorage.set(KEY_STORAGE_KEY, 'key');

await AuthClient.create({ storage });

expect(storage.set as jest.Mock).toBeCalledTimes(2);
expect((storage.set as jest.Mock).mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"delegation",
"test",
],
Array [
"identity",
"key",
],
]
`);
});
});
22 changes: 21 additions & 1 deletion packages/auth-client/src/index.ts
Expand Up @@ -21,6 +21,7 @@ import {
KEY_STORAGE_DELEGATION,
KEY_STORAGE_KEY,
KEY_VECTOR,
LocalStorage,
} from './storage';

export { IdbStorage, LocalStorage } from './storage';
Expand Down Expand Up @@ -193,7 +194,26 @@ export class AuthClient {
if (options.identity) {
key = options.identity;
} else {
const maybeIdentityStorage = await storage.get(KEY_STORAGE_KEY);
let maybeIdentityStorage = await storage.get(KEY_STORAGE_KEY);
if (!maybeIdentityStorage) {
// Attempt to migrate from localstorage
try {
const fallbackLocalStorage = new LocalStorage('ic');
const localChain = await fallbackLocalStorage.get(KEY_STORAGE_DELEGATION);
const localKey = await fallbackLocalStorage.get(KEY_STORAGE_KEY);
if (localChain && localKey) {
console.log('Discovered an identity stored in localstorage. Migrating to IndexedDB');
await storage.set(KEY_STORAGE_DELEGATION, localChain);
await storage.set(KEY_STORAGE_KEY, localKey);
maybeIdentityStorage = localChain;
// clean up
await fallbackLocalStorage.remove(KEY_STORAGE_DELEGATION);
await fallbackLocalStorage.remove(KEY_STORAGE_KEY);
}
} catch (error) {
console.error('error while attempting to recover localstorage: ' + error);
}
}
if (maybeIdentityStorage) {
try {
key = Ed25519KeyIdentity.fromJSON(maybeIdentityStorage);
Expand Down

0 comments on commit b550f81

Please sign in to comment.