Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build: agent-js v0.13.2 with idb breaking change #1247

Merged
merged 28 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
468552c
build: read session from indexeddb
peterpeterparker Aug 15, 2022
cd753fe
Updating frontend formatting
Aug 15, 2022
02ef012
Merge branch 'main' into build/agent-js-indexeddb
peterpeterparker Aug 16, 2022
6629fec
build: bump agent-js and ic-js next
peterpeterparker Aug 16, 2022
b2c5f1e
build: redo next
peterpeterparker Aug 16, 2022
c0f8488
test: mock idb with fake-indexeddb
peterpeterparker Aug 16, 2022
77986e5
Updating frontend formatting
Aug 16, 2022
0e5ee1f
feat: clear idb
peterpeterparker Aug 16, 2022
0f5a86e
Merge remote-tracking branch 'origin/build/agent-js-indexeddb' into b…
peterpeterparker Aug 16, 2022
e462a43
Updating frontend formatting
Aug 16, 2022
c274574
test: auth key cleared
peterpeterparker Aug 16, 2022
2c8d94f
Merge remote-tracking branch 'origin/build/agent-js-indexeddb' into b…
peterpeterparker Aug 16, 2022
0d02a2e
feat: replace localstorage listener with auth web worker cron
peterpeterparker Aug 16, 2022
98c3a47
Updating frontend formatting
Aug 16, 2022
bafbc62
test: mock call to auth client
peterpeterparker Aug 16, 2022
6665dec
Merge remote-tracking branch 'origin/build/agent-js-indexeddb' into b…
peterpeterparker Aug 16, 2022
050b736
Updating frontend formatting
Aug 16, 2022
8f7fe8d
Merge branch 'main' into build/agent-js-indexeddb
peterpeterparker Aug 17, 2022
c81eee4
feat: defer read and clear idb work to auth-client
peterpeterparker Aug 17, 2022
0d3f3df
test: agent-js clear idb
peterpeterparker Aug 17, 2022
b0a3db8
Merge branch 'main' into build/agent-js-indexeddb
peterpeterparker Aug 23, 2022
af50ee1
build: bump agent-js v0.13.2 and ic-js
peterpeterparker Aug 23, 2022
ad3301f
docs: typo
peterpeterparker Aug 23, 2022
4805c7f
chore: lint
peterpeterparker Aug 23, 2022
fa0f1a2
docs: remove unused comment
peterpeterparker Aug 24, 2022
feee975
Merge branch 'main' into build/agent-js-indexeddb
peterpeterparker Aug 24, 2022
2e85f34
Merge branch 'main' into build/agent-js-indexeddb
peterpeterparker Aug 24, 2022
45c9db1
Merge branch 'main' into build/agent-js-indexeddb
peterpeterparker Aug 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ module.exports = {
transformIgnorePatterns: [
"<rootDir>/node_modules/(?!(@dfinity/gix-components))",
],
setupFiles: ["fake-indexeddb/auto"],
};
944 changes: 539 additions & 405 deletions frontend/package-lock.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "TZ=UTC jest",
"test:watch": "TZ=UTC npm run test -- --watchAll",
"update:next": "npm update @dfinity/nns @dfinity/sns @dfinity/utils",
"update:agent": "npm rm @dfinity/agent @dfinity/auth-client @dfinity/authentication @dfinity/candid @dfinity/identity @dfinity/principal && npm i @dfinity/agent @dfinity/auth-client @dfinity/authentication @dfinity/candid @dfinity/identity @dfinity/principal",
"update:agent": "npm rm @dfinity/agent @dfinity/auth-client @dfinity/authentication @dfinity/candid @dfinity/identity @dfinity/principal @dfinity/nns @dfinity/sns @dfinity/utils && npm i @dfinity/agent @dfinity/auth-client @dfinity/authentication @dfinity/candid @dfinity/identity @dfinity/principal @dfinity/nns@next @dfinity/sns@next @dfinity/utils@next",
peterpeterparker marked this conversation as resolved.
Show resolved Hide resolved
"update:gix": "npm update @dfinity/gix-components"
},
"devDependencies": {
Expand All @@ -38,6 +38,7 @@
"autoprefixer": "^10.4.7",
"eslint": "^8.15.0",
"eslint-plugin-svelte3": "^4.0.0",
"fake-indexeddb": "^4.0.0",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.1.0",
"jest-mock-extended": "^2.0.6",
Expand All @@ -62,14 +63,14 @@
"typescript": "^4.6.4"
},
"dependencies": {
"@dfinity/agent": "^0.12.2",
"@dfinity/auth-client": "^0.12.2",
"@dfinity/authentication": "^0.12.2",
"@dfinity/candid": "^0.12.2",
"@dfinity/agent": "^0.13.2",
"@dfinity/auth-client": "^0.13.2",
"@dfinity/authentication": "^0.13.2",
"@dfinity/candid": "^0.13.2",
"@dfinity/gix-components": "next",
"@dfinity/identity": "^0.12.2",
"@dfinity/identity": "^0.13.2",
"@dfinity/nns": "next",
"@dfinity/principal": "^0.12.2",
"@dfinity/principal": "^0.13.2",
"@dfinity/sns": "next",
"@dfinity/utils": "next",
"@ledgerhq/hw-transport-node-hid-noevents": "^6.27.1",
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/lib/components/common/Guard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

<!-- storage: on every change in local storage we sync the auth state -->
peterpeterparker marked this conversation as resolved.
Show resolved Hide resolved
<!-- popstate: browser back button has been clicked, we reflect the new browser url to the route -->
<svelte:window
on:storage={async () => await authStore.sync()}
on:popstate={() => routeStore.update({ path: routePath() })}
/>
<svelte:window on:popstate={() => routeStore.update({ path: routePath() })} />

{#await syncAuthStore()}
<Spinner />
Expand Down
9 changes: 3 additions & 6 deletions frontend/src/lib/services/auth.services.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Identity } from "@dfinity/agent";
import { get } from "svelte/store";
import { authStore } from "../stores/auth.store";
import { themeStore } from "../stores/theme.store";
import { toastsStore } from "../stores/toasts.store";
import type { ToastLevel, ToastMsg } from "../types/toast";
import { replaceHistory } from "../utils/route.utils";
Expand All @@ -20,12 +19,10 @@ export const logout = async ({
appendMsgToUrl(msg);
}

// We preserve the anonymous theme information only so that user sign-in with same theme next time
const { theme: storageTheme }: Storage = localStorage;
// Auth: Delegation and identity are cleared from indexedDB by agent-js so, we do not need to clear these

window.localStorage.clear();

themeStore.select(storageTheme);
// Preferences: We do not clear local storage as well. It contains anonymous information such as the selected theme.
// Information the user want to preserve across sign-in. e.g. if I select the light theme, logout and sign-in again, I am happy if the dapp still uses the light theme.

// We reload the page to make sure all the states are cleared
window.location.reload();
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/lib/services/worker.services.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { AuthStore } from "../stores/auth.store";
import type { PostMessageEventData } from "../types/post-messages";
import { localStorageAuth } from "../utils/auth.utils";
import { logout } from "./auth.services";

const initWorker = () => {
Expand Down Expand Up @@ -31,7 +30,6 @@ const initWorker = () => {

worker.postMessage({
msg: "nnsStartIdleTimer",
data: await localStorageAuth(),
});
},
};
Expand Down
18 changes: 3 additions & 15 deletions frontend/src/lib/stores/auth.store.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import type { Identity } from "@dfinity/agent";
import { AuthClient } from "@dfinity/auth-client";
import type { AuthClient } from "@dfinity/auth-client";
import { writable } from "svelte/store";
import {
AUTH_SESSION_DURATION,
IDENTITY_SERVICE_URL,
} from "../constants/identity.constants";
import { createAuthClient } from "../utils/auth.utils";

export interface AuthStore {
identity: Identity | undefined | null;
}

/**
* Create an AuthClient to manage authentication and identity.
* - Session duration is 30min (AUTH_SESSION_DURATION).
* - Disable idle manager that sign-out in case of inactivity after default 10min to avoid UX issues if multiple tabs are used as we observe the storage and sync the delegation on any changes
*/
const createAuthClient = (): Promise<AuthClient> =>
AuthClient.create({
idleOptions: {
disableIdle: true,
disableDefaultIdleCallback: true,
},
});

/**
* A store to handle authentication and the identity of the user.
*
Expand All @@ -38,7 +26,7 @@ const createAuthClient = (): Promise<AuthClient> =>
*
* - signOut: call auth-client log out and set null in the store. started with a user interaction ("click on a button")
*
* note: clearing the local storage does not happen in the state management but afterwards in its caller function (see <Logout/>)
* note: clearing idb auth keys does not happen in the state management but afterwards in its caller function (see <Logout/>)
*
*/
const initAuthStore = () => {
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/lib/types/auth.ts

This file was deleted.

3 changes: 0 additions & 3 deletions frontend/src/lib/types/post-messages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import type { LocalStorageAuth } from "./auth";

export interface PostMessageEventData {
msg: "nnsStartIdleTimer" | "nnsStopIdleTimer" | "nnsSignOut";
data?: LocalStorageAuth;
}
26 changes: 13 additions & 13 deletions frontend/src/lib/utils/auth.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Identity } from "@dfinity/agent";
import { LocalStorage } from "@dfinity/auth-client";
import type { LocalStorageAuth } from "../types/auth";
import { AuthClient } from "@dfinity/auth-client";

/**
* The user is signed in when the identity is not undefined and not null.
Expand All @@ -12,14 +11,15 @@ import type { LocalStorageAuth } from "../types/auth";
export const isSignedIn = (identity: Identity | undefined | null): boolean =>
identity !== undefined && identity !== null;

export const localStorageAuth = async (): Promise<LocalStorageAuth> => {
const storage: LocalStorage = new LocalStorage("ic-");

const identityKey: string | null = await storage.get("identity");
const delegationChain: string | null = await storage.get("delegation");

return {
identityKey,
delegationChain,
};
};
/**
* Create an AuthClient to manage authentication and identity.
* - Session duration is 30min (AUTH_SESSION_DURATION).
* - Disable idle manager that sign-out in case of inactivity after default 10min to avoid UX issues if multiple tabs are used as we observe the storage and sync the delegation on any changes
*/
export const createAuthClient = (): Promise<AuthClient> =>
AuthClient.create({
idleOptions: {
disableIdle: true,
disableDefaultIdleCallback: true,
},
});
54 changes: 42 additions & 12 deletions frontend/src/lib/workers/auth.worker.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { IdbStorage, type AuthClient } from "@dfinity/auth-client";
import { isDelegationValid } from "@dfinity/authentication";
import { DelegationChain } from "@dfinity/identity";
import type { LocalStorageAuth } from "../types/auth";
import { createAuthClient } from "../utils/auth.utils";

let timer: NodeJS.Timeout | undefined = undefined;

export const startIdleTimer = (data?: LocalStorageAuth) =>
(timer = setInterval(() => onIdleSignOut(data), 1000));
/**
* The timer is executed only if user has signed in
*/
export const startIdleTimer = () =>
(timer = setInterval(async () => await onIdleSignOut(), 1000));

export const stopIdleTimer = () => {
if (!timer) {
Expand All @@ -16,21 +20,47 @@ export const stopIdleTimer = () => {
timer = undefined;
};

const onIdleSignOut = (data?: LocalStorageAuth) => {
if (!data) {
const onIdleSignOut = async () => {
const [auth, delegation] = await Promise.all([
checkAuthentication(),
checkDelegationChain(),
]);

// Both identity and delegation are alright, so all good
if (auth && delegation) {
return;
}

const { delegationChain } = data;
logout();
};

if (delegationChain === null) {
return;
}
/**
* If user is not authenticated - i.e. no identity or anonymous and there is no valid delegation chain, then identity is not valid
*
* @returns true if authenticated
*/
const checkAuthentication = async (): Promise<boolean> => {
const authClient: AuthClient = await createAuthClient();
return authClient.isAuthenticated();
};

if (isDelegationValid(DelegationChain.fromJSON(delegationChain))) {
return;
}
/**
* If there is no delegation or if not valid, then delegation is not valid
*
* @returns true if delegation is valid
*/
const checkDelegationChain = async (): Promise<boolean> => {
const idbStorage: IdbStorage = new IdbStorage();
const delegationChain: string | null = await idbStorage.get("delegation");
peterpeterparker marked this conversation as resolved.
Show resolved Hide resolved

return (
delegationChain !== null &&
isDelegationValid(DelegationChain.fromJSON(delegationChain))
);
};

// We do the logout on the client side because we reload the window to reload stores afterwards
const logout = () => {
// Clear timer to not emit sign-out multiple times
stopIdleTimer();

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/tests/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ describe("App", () => {
jest
.spyOn(authStore, "subscribe")
.mockImplementation(mutableMockAuthStoreSubscribe);

jest.spyOn(authStore, "sync").mockImplementation(() => Promise.resolve());
});

afterAll(() => {
Expand Down
12 changes: 0 additions & 12 deletions frontend/src/tests/lib/components/common/Guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,4 @@ describe("Guard", () => {
expect(container.querySelector("svg")).not.toBeNull();
expect(container.querySelector("circle")).not.toBeNull();
});

it("should sync auth on localstorage changes", () => {
const spy = jest
.spyOn(authStore, "sync")
.mockImplementation(() => Promise.resolve());

render(Guard);

window.localStorage.setItem("test", "test");

expect(spy).toHaveBeenCalled();
});
});