diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html
index 1b980f8bf..4e86bf006 100644
--- a/docs/generated/changelog.html
+++ b/docs/generated/changelog.html
@@ -10,6 +10,16 @@
Agent-JS Changelog
+ Version 0.12.3
+
+ -
+ AuthClient now uses IndexedDb by default. To use localStorage, import LocalStorage
+ provider and pass it during AuthClient.create().
+
+
+ - Also offers a generic Indexed Db keyval store, IdbKeyVal
+
+
Version 0.12.2
-
diff --git a/package-lock.json b/package-lock.json
index d6f8fe884..0fd89ee38 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4724,7 +4724,8 @@
},
"node_modules/@peculiar/webcrypto": {
"version": "1.4.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz",
+ "integrity": "sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg==",
"dependencies": {
"@peculiar/asn1-schema": "^2.1.6",
"@peculiar/json-schema": "^1.1.12",
@@ -6474,6 +6475,15 @@
"node": ">= 0.6.0"
}
},
+ "node_modules/base64-arraybuffer-es6": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz",
+ "integrity": "sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"funding": [
@@ -9312,6 +9322,15 @@
"node": "> 0.1.90"
}
},
+ "node_modules/fake-indexeddb": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz",
+ "integrity": "sha512-oCfWSJ/qvQn1XPZ8SHX6kY3zr1t+bN7faZ/lltGY0SBGhFOPXnWf0+pbO/MOAgfMx6khC2gK3S/bvAgQpuQHDQ==",
+ "dev": true,
+ "dependencies": {
+ "realistic-structured-clone": "^3.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"license": "MIT"
@@ -10271,6 +10290,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/idb": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz",
+ "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg=="
+ },
"node_modules/idb-keyval": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.0.tgz",
@@ -15207,6 +15231,32 @@
"dev": true,
"license": "WTFPL"
},
+ "node_modules/realistic-structured-clone": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz",
+ "integrity": "sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==",
+ "dev": true,
+ "dependencies": {
+ "domexception": "^1.0.1",
+ "typeson": "^6.1.0",
+ "typeson-registry": "^1.0.0-alpha.20"
+ }
+ },
+ "node_modules/realistic-structured-clone/node_modules/domexception": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
+ "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
+ "dev": true,
+ "dependencies": {
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "node_modules/realistic-structured-clone/node_modules/webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ },
"node_modules/rechoir": {
"version": "0.7.1",
"license": "MIT",
@@ -16804,6 +16854,64 @@
"node": ">=4.2.0"
}
},
+ "node_modules/typeson": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz",
+ "integrity": "sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
+ "node_modules/typeson-registry": {
+ "version": "1.0.0-alpha.39",
+ "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz",
+ "integrity": "sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==",
+ "dev": true,
+ "dependencies": {
+ "base64-arraybuffer-es6": "^0.7.0",
+ "typeson": "^6.0.0",
+ "whatwg-url": "^8.4.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/typeson-registry/node_modules/tr46": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
+ "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/typeson-registry/node_modules/webidl-conversions": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
+ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.4"
+ }
+ },
+ "node_modules/typeson-registry/node_modules/whatwg-url": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz",
+ "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.7.0",
+ "tr46": "^2.1.0",
+ "webidl-conversions": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/unbox-primitive": {
"version": "1.0.1",
"license": "MIT",
@@ -17980,16 +18088,18 @@
"license": "Apache-2.0",
"dependencies": {
"@types/jest": "^28.1.4",
+ "idb": "^7.0.2",
"jest": "^28.1.2",
"ts-jest": "^28.0.5",
"ts-node": "^10.8.2"
},
"devDependencies": {
- "@trust/webcrypto": "^0.9.2",
+ "@peculiar/webcrypto": "^1.4.0",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"eslint": "^8.19.0",
"eslint-plugin-jsdoc": "^39.3.3",
+ "fake-indexeddb": "^4.0.0",
"jest-environment-jsdom": "^28.1.2",
"text-encoding": "^0.7.0",
"tslint": "^5.20.0",
@@ -19662,12 +19772,14 @@
"@dfinity/auth-client": {
"version": "file:packages/auth-client",
"requires": {
- "@trust/webcrypto": "^0.9.2",
+ "@peculiar/webcrypto": "^1.4.0",
"@types/jest": "^28.1.4",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"eslint": "^8.19.0",
"eslint-plugin-jsdoc": "^39.3.3",
+ "fake-indexeddb": "^4.0.0",
+ "idb": "^7.0.2",
"jest": "^28.1.2",
"jest-environment-jsdom": "^28.1.2",
"text-encoding": "^0.7.0",
@@ -22073,6 +22185,8 @@
},
"@peculiar/webcrypto": {
"version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz",
+ "integrity": "sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg==",
"requires": {
"@peculiar/asn1-schema": "^2.1.6",
"@peculiar/json-schema": "^1.1.12",
@@ -23332,6 +23446,12 @@
"base64-arraybuffer": {
"version": "0.2.0"
},
+ "base64-arraybuffer-es6": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz",
+ "integrity": "sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==",
+ "dev": true
+ },
"base64-js": {
"version": "1.5.1"
},
@@ -25259,6 +25379,15 @@
"version": "0.1.8",
"dev": true
},
+ "fake-indexeddb": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz",
+ "integrity": "sha512-oCfWSJ/qvQn1XPZ8SHX6kY3zr1t+bN7faZ/lltGY0SBGhFOPXnWf0+pbO/MOAgfMx6khC2gK3S/bvAgQpuQHDQ==",
+ "dev": true,
+ "requires": {
+ "realistic-structured-clone": "^3.0.0"
+ }
+ },
"fast-deep-equal": {
"version": "3.1.3"
},
@@ -25927,6 +26056,11 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
+ "idb": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz",
+ "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg=="
+ },
"idb-keyval": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.0.tgz",
@@ -29130,6 +29264,34 @@
"version": "1.2.0",
"dev": true
},
+ "realistic-structured-clone": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz",
+ "integrity": "sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==",
+ "dev": true,
+ "requires": {
+ "domexception": "^1.0.1",
+ "typeson": "^6.1.0",
+ "typeson-registry": "^1.0.0-alpha.20"
+ },
+ "dependencies": {
+ "domexception": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
+ "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
+ "dev": true,
+ "requires": {
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ }
+ }
+ },
"rechoir": {
"version": "0.7.1",
"requires": {
@@ -30142,6 +30304,51 @@
"typescript": {
"version": "4.7.4"
},
+ "typeson": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz",
+ "integrity": "sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==",
+ "dev": true
+ },
+ "typeson-registry": {
+ "version": "1.0.0-alpha.39",
+ "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz",
+ "integrity": "sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==",
+ "dev": true,
+ "requires": {
+ "base64-arraybuffer-es6": "^0.7.0",
+ "typeson": "^6.0.0",
+ "whatwg-url": "^8.4.0"
+ },
+ "dependencies": {
+ "tr46": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
+ "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.1"
+ }
+ },
+ "webidl-conversions": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
+ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz",
+ "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.7.0",
+ "tr46": "^2.1.0",
+ "webidl-conversions": "^6.1.0"
+ }
+ }
+ }
+ },
"unbox-primitive": {
"version": "1.0.1",
"requires": {
diff --git a/packages/auth-client/package.json b/packages/auth-client/package.json
index e59f2be5f..720d05817 100644
--- a/packages/auth-client/package.json
+++ b/packages/auth-client/package.json
@@ -51,11 +51,12 @@
"@dfinity/principal": "^0.12.2"
},
"devDependencies": {
- "@trust/webcrypto": "^0.9.2",
+ "@peculiar/webcrypto": "^1.4.0",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"eslint": "^8.19.0",
"eslint-plugin-jsdoc": "^39.3.3",
+ "fake-indexeddb": "^4.0.0",
"jest-environment-jsdom": "^28.1.2",
"text-encoding": "^0.7.0",
"tslint": "^5.20.0",
@@ -65,6 +66,7 @@
},
"dependencies": {
"@types/jest": "^28.1.4",
+ "idb": "^7.0.2",
"jest": "^28.1.2",
"ts-jest": "^28.0.5",
"ts-node": "^10.8.2"
diff --git a/packages/auth-client/src/db.test.ts b/packages/auth-client/src/db.test.ts
new file mode 100644
index 000000000..61bd782ed
--- /dev/null
+++ b/packages/auth-client/src/db.test.ts
@@ -0,0 +1,35 @@
+import 'fake-indexeddb/auto';
+import { IdbKeyVal } from './db';
+
+let testCounter = 0;
+
+const testDb = async () => {
+ return await IdbKeyVal.create({
+ dbName: 'db-' + testCounter,
+ storeName: 'store-' + testCounter,
+ });
+};
+
+beforeEach(() => {
+ testCounter += 1;
+});
+
+describe('indexeddb wrapper', () => {
+ it('should store a basic key value', async () => {
+ const db = await testDb();
+ const shouldSet = async () => await db.set('testKey', 'testValue');
+ expect(shouldSet).not.toThrow();
+
+ expect(await db.get('testKey')).toBe('testValue');
+ });
+ it('should support removing a value', async () => {
+ const db = await testDb();
+ await db.set('testKey', 'testValue');
+
+ expect(await db.get('testKey')).toBe('testValue');
+
+ await db.remove('testKey');
+
+ expect(await db.get('testKey')).toBe(null);
+ });
+});
diff --git a/packages/auth-client/src/db.ts b/packages/auth-client/src/db.ts
new file mode 100644
index 000000000..adc3f8f73
--- /dev/null
+++ b/packages/auth-client/src/db.ts
@@ -0,0 +1,110 @@
+import { openDB, IDBPDatabase } from 'idb';
+import { KEY_STORAGE_DELEGATION, KEY_STORAGE_KEY } from './storage';
+
+type Database = IDBPDatabase;
+type IDBValidKey = string | number | Date | BufferSource | IDBValidKey[];
+const AUTH_DB_NAME = 'auth-client-db';
+const OBJECT_STORE_NAME = 'ic-keyval';
+
+const _openDbStore = async (
+ dbName = AUTH_DB_NAME,
+ storeName = OBJECT_STORE_NAME,
+ version: number,
+) => {
+ // Clear legacy stored delegations
+ if (localStorage && localStorage.getItem(KEY_STORAGE_DELEGATION)) {
+ localStorage.removeItem(KEY_STORAGE_DELEGATION);
+ localStorage.removeItem(KEY_STORAGE_KEY);
+ }
+ return await openDB(dbName, version, {
+ upgrade: database => {
+ database.objectStoreNames;
+ if (database.objectStoreNames.contains(storeName)) {
+ database.clear(storeName);
+ }
+ database.createObjectStore(storeName);
+ },
+ });
+};
+
+async function _getValue(
+ db: Database,
+ storeName: string,
+ key: IDBValidKey,
+): Promise {
+ return await db.get(storeName, key);
+}
+
+async function _setValue(
+ db: Database,
+ storeName: string,
+ key: IDBValidKey,
+ value: T,
+): Promise {
+ return await db.put(storeName, value, key);
+}
+
+async function _removeValue(db: Database, storeName: string, key: IDBValidKey): Promise {
+ return await db.delete(storeName, key);
+}
+
+export type DBCreateOptions = {
+ dbName?: string;
+ storeName?: string;
+ version?: number;
+};
+
+/**
+ * Simple Key Value store
+ * Defaults to `'auth-client-db'` with an object store of `'ic-keyval'`
+ */
+export class IdbKeyVal {
+ /**
+ *
+ * @param {DBCreateOptions} options {@link DbCreateOptions}
+ * @param {DBCreateOptions['dbName']} options.dbName name for the indexeddb database
+ * @default 'auth-client-db'
+ * @param {DBCreateOptions['storeName']} options.storeName name for the indexeddb Data Store
+ * @default 'ic-keyval'
+ * @param {DBCreateOptions['version']} options.version version of the database. Increment to safely upgrade
+ * @constructs an {@link IdbKeyVal}
+ */
+ public static async create(options?: DBCreateOptions): Promise {
+ const { dbName = AUTH_DB_NAME, storeName = OBJECT_STORE_NAME, version = 1 } = options ?? {};
+ const db = await _openDbStore(dbName, storeName, version);
+ return new IdbKeyVal(db, storeName);
+ }
+
+ // Do not use - instead prefer create
+ private constructor(private _db: Database, private _storeName: string) {}
+
+ /**
+ * Basic setter
+ * @param {IDBValidKey} key string | number | Date | BufferSource | IDBValidKey[]
+ * @param value value to set
+ * @returns void
+ */
+ public async set(key: IDBValidKey, value: T) {
+ return await _setValue(this._db, this._storeName, key, value);
+ }
+ /**
+ * Basic getter
+ * Pass in a type T for type safety if you know the type the value will have if it is found
+ * @param {IDBValidKey} key string | number | Date | BufferSource | IDBValidKey[]
+ * @returns `Promise`
+ * @example
+ * await get('exampleKey') -> 'exampleValue'
+ */
+ public async get(key: IDBValidKey): Promise {
+ return (await _getValue(this._db, this._storeName, key)) ?? null;
+ }
+
+ /**
+ * Remove a key
+ * @param key {@link IDBValidKey}
+ * @returns void
+ */
+ public async remove(key: IDBValidKey) {
+ return await _removeValue(this._db, this._storeName, key);
+ }
+}
diff --git a/packages/auth-client/src/index.test.ts b/packages/auth-client/src/index.test.ts
index d73ab8ae9..c1ec25475 100644
--- a/packages/auth-client/src/index.test.ts
+++ b/packages/auth-client/src/index.test.ts
@@ -1,9 +1,11 @@
+import 'fake-indexeddb/auto';
import { Actor, HttpAgent } from '@dfinity/agent';
import { AgentError } from '@dfinity/agent/lib/cjs/errors';
import { IDL } from '@dfinity/candid';
import { Ed25519KeyIdentity } from '@dfinity/identity';
import { Principal } from '@dfinity/principal';
-import { AuthClient, AuthClientStorage, ERROR_USER_INTERRUPT } from './index';
+import { AuthClient, ERROR_USER_INTERRUPT, IdbStorage } from './index';
+import { AuthClientStorage } from './storage';
/**
* A class for mocking the IDP service.
@@ -75,7 +77,6 @@ describe('Auth Client', () => {
fetch,
toString: jest.fn(() => 'http://localhost:8000'),
};
- const newLocation = window.location;
const identity = Ed25519KeyIdentity.generate();
const mockFetch: jest.Mock = jest.fn();
@@ -321,6 +322,15 @@ describe('Auth Client', () => {
});
});
+describe('IdbStorage', () => {
+ it('should handle get and set', async () => {
+ const storage = new IdbStorage();
+
+ await storage.set('testKey', 'testValue');
+ expect(await storage.get('testKey')).toBe('testValue');
+ });
+});
+
// A minimal interface of our interactions with the Window object of the IDP.
interface IdpWindow {
postMessage(message: { kind: string }): void;
diff --git a/packages/auth-client/src/index.ts b/packages/auth-client/src/index.ts
index efc25cf7d..fcf7b1c35 100644
--- a/packages/auth-client/src/index.ts
+++ b/packages/auth-client/src/index.ts
@@ -15,9 +15,17 @@ import {
} from '@dfinity/identity';
import { Principal } from '@dfinity/principal';
import { IdleManager, IdleManagerOptions } from './idleManager';
+import {
+ AuthClientStorage,
+ IdbStorage,
+ KEY_STORAGE_DELEGATION,
+ KEY_STORAGE_KEY,
+ KEY_VECTOR,
+} from './storage';
+
+export { IdbStorage, LocalStorage } from './storage';
+export { IdbKeyVal, DBCreateOptions } from './db';
-const KEY_LOCALSTORAGE_KEY = 'identity';
-const KEY_LOCALSTORAGE_DELEGATION = 'delegation';
const IDENTITY_PROVIDER_DEFAULT = 'https://identity.ic0.app';
const IDENTITY_PROVIDER_ENDPOINT = '#authorize';
@@ -34,7 +42,7 @@ export interface AuthClientCreateOptions {
*/
identity?: SignIdentity;
/**
- * Optional storage with get, set, and remove. Uses LocalStorage by default
+ * Optional storage with get, set, and remove. Uses {@link IdbStorage} by default
*/
storage?: AuthClientStorage;
/**
@@ -91,17 +99,6 @@ export interface AuthClientLoginOptions {
onError?: ((error?: string) => void) | ((error?: string) => Promise);
}
-/**
- * Interface for persisting user authentication data
- */
-export interface AuthClientStorage {
- get(key: string): Promise;
-
- set(key: string, value: string): Promise;
-
- remove(key: string): Promise;
-}
-
interface InternetIdentityAuthRequest {
kind: 'authorize-client';
sessionPublicKey: Uint8Array;
@@ -122,50 +119,6 @@ interface InternetIdentityAuthResponseSuccess {
userPublicKey: Uint8Array;
}
-async function _deleteStorage(storage: AuthClientStorage) {
- await storage.remove(KEY_LOCALSTORAGE_KEY);
- await storage.remove(KEY_LOCALSTORAGE_DELEGATION);
-}
-
-export class LocalStorage implements AuthClientStorage {
- constructor(public readonly prefix = 'ic-', private readonly _localStorage?: Storage) {}
-
- public get(key: string): Promise {
- return Promise.resolve(this._getLocalStorage().getItem(this.prefix + key));
- }
-
- public set(key: string, value: string): Promise {
- this._getLocalStorage().setItem(this.prefix + key, value);
- return Promise.resolve();
- }
-
- public remove(key: string): Promise {
- this._getLocalStorage().removeItem(this.prefix + key);
- return Promise.resolve();
- }
-
- private _getLocalStorage() {
- if (this._localStorage) {
- return this._localStorage;
- }
-
- const ls =
- typeof window === 'undefined'
- ? typeof global === 'undefined'
- ? typeof self === 'undefined'
- ? undefined
- : self.localStorage
- : global.localStorage
- : window.localStorage;
-
- if (!ls) {
- throw new Error('Could not find local storage.');
- }
-
- return ls;
- }
-}
-
interface AuthReadyMessage {
kind: 'authorize-ready';
}
@@ -234,13 +187,13 @@ export class AuthClient {
idleOptions?: IdleOptions;
} = {},
): Promise {
- const storage = options.storage ?? new LocalStorage('ic-');
+ const storage = options.storage ?? new IdbStorage();
let key: null | SignIdentity = null;
if (options.identity) {
key = options.identity;
} else {
- const maybeIdentityStorage = await storage.get(KEY_LOCALSTORAGE_KEY);
+ const maybeIdentityStorage = await storage.get(KEY_STORAGE_KEY);
if (maybeIdentityStorage) {
try {
key = Ed25519KeyIdentity.fromJSON(maybeIdentityStorage);
@@ -256,7 +209,7 @@ export class AuthClient {
if (key) {
try {
- const chainStorage = await storage.get(KEY_LOCALSTORAGE_DELEGATION);
+ const chainStorage = await storage.get(KEY_STORAGE_DELEGATION);
if (options.identity) {
identity = options.identity;
@@ -409,7 +362,7 @@ export class AuthClient {
// Create a new key (whether or not one was in storage).
key = Ed25519KeyIdentity.generate();
this._key = key;
- await this._storage.set(KEY_LOCALSTORAGE_KEY, JSON.stringify(key));
+ await this._storage.set(KEY_STORAGE_KEY, JSON.stringify(key));
}
// Set default maxTimeToLive to 8 hours
@@ -485,10 +438,7 @@ export class AuthClient {
// it a sync function. Having _handleSuccess as an async function
// messes up the jest tests for some reason.
if (this._chain) {
- await this._storage.set(
- KEY_LOCALSTORAGE_DELEGATION,
- JSON.stringify(this._chain.toJSON()),
- );
+ await this._storage.set(KEY_STORAGE_DELEGATION, JSON.stringify(this._chain.toJSON()));
}
} catch (err) {
this._handleFailure((err as Error).message, options?.onError);
@@ -534,3 +484,9 @@ export class AuthClient {
}
}
}
+
+async function _deleteStorage(storage: AuthClientStorage) {
+ await storage.remove(KEY_STORAGE_KEY);
+ await storage.remove(KEY_STORAGE_DELEGATION);
+ await storage.remove(KEY_VECTOR);
+}
diff --git a/packages/auth-client/src/storage.ts b/packages/auth-client/src/storage.ts
new file mode 100644
index 000000000..2a43876a3
--- /dev/null
+++ b/packages/auth-client/src/storage.ts
@@ -0,0 +1,96 @@
+import { IdbKeyVal } from './db';
+
+export const KEY_STORAGE_KEY = 'identity';
+export const KEY_STORAGE_DELEGATION = 'delegation';
+export const KEY_VECTOR = 'iv';
+// Increment if any fields are modified
+export const DB_VERSION = 1;
+
+/**
+ * Interface for persisting user authentication data
+ */
+export interface AuthClientStorage {
+ get(key: string): Promise;
+
+ set(key: string, value: string): Promise;
+
+ remove(key: string): Promise;
+}
+
+/**
+ * Legacy implementation of AuthClientStorage, for use where IndexedDb is not available
+ */
+export class LocalStorage implements AuthClientStorage {
+ constructor(public readonly prefix = 'ic-', private readonly _localStorage?: Storage) {}
+
+ public get(key: string): Promise {
+ return Promise.resolve(this._getLocalStorage().getItem(this.prefix + key));
+ }
+
+ public set(key: string, value: string): Promise {
+ this._getLocalStorage().setItem(this.prefix + key, value);
+ return Promise.resolve();
+ }
+
+ public remove(key: string): Promise {
+ this._getLocalStorage().removeItem(this.prefix + key);
+ return Promise.resolve();
+ }
+
+ private _getLocalStorage() {
+ if (this._localStorage) {
+ return this._localStorage;
+ }
+
+ const ls =
+ typeof window === 'undefined'
+ ? typeof global === 'undefined'
+ ? typeof self === 'undefined'
+ ? undefined
+ : self.localStorage
+ : global.localStorage
+ : window.localStorage;
+
+ if (!ls) {
+ throw new Error('Could not find local storage.');
+ }
+
+ return ls;
+ }
+}
+
+/**
+ * IdbStorage is an interface for simple storage of string key-value pairs built on {@link IdbKeyVal}
+ *
+ * It replaces {@link LocalStorage}
+ * @see implements {@link AuthClientStorage}
+ */
+export class IdbStorage implements AuthClientStorage {
+ // Intializes a KeyVal on first request
+ private initializedDb: IdbKeyVal | undefined;
+ get _db(): Promise {
+ return new Promise(resolve => {
+ if (this.initializedDb) resolve(this.initializedDb);
+ IdbKeyVal.create({ version: DB_VERSION }).then(db => {
+ this.initializedDb = db;
+ resolve(db);
+ });
+ });
+ }
+
+ public async get(key: string): Promise {
+ const db = await this._db;
+ return await db.get(key);
+ // return (await db.get(key)) ?? null;
+ }
+
+ public async set(key: string, value: string): Promise {
+ const db = await this._db;
+ await db.set(key, value);
+ }
+
+ public async remove(key: string): Promise {
+ const db = await this._db;
+ await db.remove(key);
+ }
+}
diff --git a/packages/auth-client/test-setup.ts b/packages/auth-client/test-setup.ts
index ec0be32c6..497f24ca5 100644
--- a/packages/auth-client/test-setup.ts
+++ b/packages/auth-client/test-setup.ts
@@ -6,8 +6,9 @@
//
// Note that we can use webpack configuration to make some features available to
// Node.js in a similar way.
-
-global.crypto = require('@trust/webcrypto');
+import { Crypto } from '@peculiar/webcrypto';
+import 'fake-indexeddb/auto';
+global.crypto = new Crypto();
global.TextEncoder = require('text-encoding').TextEncoder;
global.TextDecoder = require('text-encoding').TextDecoder;
require('whatwg-fetch');