mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(challenge modal): remember trusted board websites
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import useTrustedBoardUrlPermissionsStore from '../use-trusted-board-url-permissions-store';
|
||||
|
||||
const STORAGE_KEY = 'trusted-board-url-permissions-store';
|
||||
|
||||
describe('useTrustedBoardUrlPermissionsStore', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
useTrustedBoardUrlPermissionsStore.setState({ trustedOrigins: {} });
|
||||
});
|
||||
|
||||
it('starts without trusted origins', () => {
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().getTrustedOrigins()).toEqual([]);
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().isOriginTrusted('https://spamblocker.bitsocial.net')).toBe(false);
|
||||
});
|
||||
|
||||
it('trusts and persists a board challenge origin', () => {
|
||||
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem');
|
||||
|
||||
useTrustedBoardUrlPermissionsStore.getState().trustOrigin('https://spamblocker.bitsocial.net', 'spamblocker.bitsocial.net', null, 1_000);
|
||||
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().isOriginTrusted('https://spamblocker.bitsocial.net', 2_000)).toBe(true);
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().getTrustedOrigins(2_000)).toEqual([
|
||||
{
|
||||
createdAt: 1_000,
|
||||
expiresAt: null,
|
||||
origin: 'https://spamblocker.bitsocial.net',
|
||||
site: 'spamblocker.bitsocial.net',
|
||||
},
|
||||
]);
|
||||
expect(setItemSpy).toHaveBeenCalledWith(STORAGE_KEY, expect.stringContaining('spamblocker.bitsocial.net'));
|
||||
|
||||
setItemSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('revokes a trusted origin', () => {
|
||||
useTrustedBoardUrlPermissionsStore.getState().trustOrigin('https://spamblocker.bitsocial.net', 'spamblocker.bitsocial.net');
|
||||
|
||||
useTrustedBoardUrlPermissionsStore.getState().revokeOrigin('https://spamblocker.bitsocial.net');
|
||||
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().isOriginTrusted('https://spamblocker.bitsocial.net')).toBe(false);
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().getTrustedOrigins()).toEqual([]);
|
||||
});
|
||||
|
||||
it('drops expired permissions when checked', () => {
|
||||
useTrustedBoardUrlPermissionsStore.getState().trustOrigin('https://old.example', 'old.example', 10, 1);
|
||||
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().isOriginTrusted('https://old.example', 11)).toBe(false);
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().trustedOrigins).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface TrustedBoardUrlPermission {
|
||||
createdAt: number;
|
||||
expiresAt: number | null;
|
||||
origin: string;
|
||||
site: string;
|
||||
}
|
||||
|
||||
interface TrustedBoardUrlPermissionsState {
|
||||
trustedOrigins: Record<string, TrustedBoardUrlPermission>;
|
||||
clearAll: () => void;
|
||||
getTrustedOrigins: (now?: number) => TrustedBoardUrlPermission[];
|
||||
isOriginTrusted: (origin: string, now?: number) => boolean;
|
||||
revokeOrigin: (origin: string) => void;
|
||||
trustOrigin: (origin: string, site: string, expiresAt?: number | null, now?: number) => void;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'trusted-board-url-permissions-store';
|
||||
|
||||
const isPermissionActive = (permission: TrustedBoardUrlPermission | undefined, now: number) =>
|
||||
!!permission && (permission.expiresAt === null || permission.expiresAt > now);
|
||||
|
||||
const removeExpiredPermissions = (trustedOrigins: Record<string, TrustedBoardUrlPermission>, now: number) =>
|
||||
Object.fromEntries(Object.entries(trustedOrigins).filter(([, permission]) => isPermissionActive(permission, now)));
|
||||
|
||||
const useTrustedBoardUrlPermissionsStore = create<TrustedBoardUrlPermissionsState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
trustedOrigins: {},
|
||||
clearAll: () => set({ trustedOrigins: {} }),
|
||||
getTrustedOrigins: (now = Date.now()) =>
|
||||
Object.values(removeExpiredPermissions(get().trustedOrigins, now)).sort((a, b) => a.site.localeCompare(b.site) || a.origin.localeCompare(b.origin)),
|
||||
isOriginTrusted: (origin, now = Date.now()) => {
|
||||
const permission = get().trustedOrigins[origin];
|
||||
if (isPermissionActive(permission, now)) return true;
|
||||
if (permission) {
|
||||
set((state) => {
|
||||
const { [origin]: _expired, ...trustedOrigins } = state.trustedOrigins;
|
||||
return { trustedOrigins };
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
revokeOrigin: (origin) =>
|
||||
set((state) => {
|
||||
const { [origin]: _removed, ...trustedOrigins } = state.trustedOrigins;
|
||||
return { trustedOrigins };
|
||||
}),
|
||||
trustOrigin: (origin, site, expiresAt = null, now = Date.now()) =>
|
||||
set((state) => ({
|
||||
trustedOrigins: {
|
||||
...state.trustedOrigins,
|
||||
[origin]: {
|
||||
createdAt: now,
|
||||
expiresAt,
|
||||
origin,
|
||||
site,
|
||||
},
|
||||
},
|
||||
})),
|
||||
}),
|
||||
{
|
||||
name: STORAGE_KEY,
|
||||
partialize: (state) => ({ trustedOrigins: state.trustedOrigins }),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export default useTrustedBoardUrlPermissionsStore;
|
||||
Reference in New Issue
Block a user