Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions apps/extension/src/storage/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ export const readPersistedState = async (): Promise<{
typeof tokenPayload[TOKEN_KEY] === "string"
? (tokenPayload[TOKEN_KEY] as string)
: null;
return { local: state, sessionToken };
const localToken = typeof state?.githubToken === "string" ? state.githubToken : null;
return { local: state, sessionToken: sessionToken ?? localToken };
};

export const writePersistedState = async (state: StoredState): Promise<void> => {
await chrome.storage.session.set({
[TOKEN_KEY]: state.githubToken,
});

const persistableState: StoredState = {
...state,
githubToken: null,
};
if (state.githubToken) {
await chrome.storage.session.set({
[TOKEN_KEY]: state.githubToken,
});
} else {
await chrome.storage.session.remove(TOKEN_KEY);
}

await chrome.storage.local.set({
[STORAGE_KEY]: persistableState,
[STORAGE_KEY]: state,
});
};

Expand Down
94 changes: 94 additions & 0 deletions apps/extension/tests/unit/storage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { clearAuthState, getStoredState, saveStoredState, type StoredState } from "@/storage";
import { STORAGE_KEY, TOKEN_KEY } from "@/storage/authSession";

type MemoryStorageArea = {
data: Record<string, unknown>;
get: (key: string) => Promise<Record<string, unknown>>;
remove: (key: string) => Promise<void>;
set: (items: Record<string, unknown>) => Promise<void>;
};

const createMemoryStorageArea = (): MemoryStorageArea => {
const data: Record<string, unknown> = {};
return {
data,
get: async (key: string) => ({ [key]: data[key] }),
remove: async (key: string) => {
delete data[key];
},
set: async (items: Record<string, unknown>) => {
Object.assign(data, items);
},
};
};

const buildAuthenticatedState = (): StoredState => ({
githubToken: "persisted-token",
auth: {
isAuthenticated: true,
username: "oliviermaghe",
method: "web",
},
settings: {
threshold: 95,
selectedRepoFullName: "MarcoAntolini/CSSHub",
selectedBranch: "main",
systemNotificationsEnabled: true,
repositoryReadmeMode: "managed-section",
},
lastSubmission: null,
lastSubmissionAccepted: null,
lastIngestion: null,
recentEvents: [],
lastSubmissionFingerprint: null,
});

describe("extension storage auth persistence", () => {
let local: MemoryStorageArea;
let session: MemoryStorageArea;

beforeEach(() => {
local = createMemoryStorageArea();
session = createMemoryStorageArea();
vi.stubGlobal("chrome", {
storage: {
local,
session,
},
});
});

it("keeps GitHub auth after browser session storage is cleared", async () => {
await saveStoredState(buildAuthenticatedState());
for (const key of Object.keys(session.data)) {
delete session.data[key];
}

const state = await getStoredState();
const persisted = local.data[STORAGE_KEY] as StoredState;

expect(persisted.githubToken).toBe("persisted-token");
expect(state.githubToken).toBe("persisted-token");
expect(state.auth).toEqual({
isAuthenticated: true,
username: "oliviermaghe",
method: "web",
});
});

it("clears the durable GitHub token on explicit disconnect", async () => {
await saveStoredState(buildAuthenticatedState());

await clearAuthState();

const persisted = local.data[STORAGE_KEY] as StoredState;
expect(persisted.githubToken).toBeNull();
expect(session.data[TOKEN_KEY]).toBeUndefined();
expect(persisted.auth).toEqual({
isAuthenticated: false,
username: null,
method: null,
});
});
});
Loading