diff --git a/jest.config.mjs b/jest.config.mjs index 6574372..2c05f9d 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -11,6 +11,10 @@ export default { transform: { '^.+\\.[tj]sx?$': ['babel-jest', { configFile: './babel.config.mjs' }], }, + moduleNameMapper: { + '^@uvdsl/solid-oidc-client-browser$': '/test/mocks/solid-oidc-client-browser.ts', + '^@uvdsl/solid-oidc-client-browser/core$': '/test/mocks/solid-oidc-client-browser.ts', + }, setupFilesAfterEnv: ['./test/helpers/setup.ts'], testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], roots: ['/src', '/test'], diff --git a/package-lock.json b/package-lock.json index b5f22fc..6ed4b54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "4.0.7", "license": "MIT", "dependencies": { - "@inrupt/solid-client-authn-browser": "^4.0.0", + "@uvdsl/solid-oidc-client-browser": "^0.2.2", "solid-namespace": "^0.5.4" }, "devDependencies": { @@ -2110,45 +2110,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@inrupt/oidc-client-ext": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@inrupt/oidc-client-ext/-/oidc-client-ext-4.0.0.tgz", - "integrity": "sha512-E32/yElFpADyWRFO6FdCyB1Ew1svsNX/fFdvHWP3qCBhSlfJVq2hMChWxs/RIRmTjHePyjT2UKEuItM09WXaWA==", - "license": "MIT", - "dependencies": { - "@inrupt/solid-client-authn-core": "^4.0.0", - "jose": "^5.1.3", - "oidc-client-ts": "^3.5.0", - "uuid": "^11.1.0" - } - }, - "node_modules/@inrupt/solid-client-authn-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@inrupt/solid-client-authn-browser/-/solid-client-authn-browser-4.0.0.tgz", - "integrity": "sha512-b7DpLMjYVMPiRv3QWqOmCeYqKL1t2THYQawuYM1zNqtN1SJGG5XEkXIy3ZQxx12tzAjeLNjH3ZAOg/CK/ehg2w==", - "license": "MIT", - "dependencies": { - "@inrupt/oidc-client-ext": "^4.0.0", - "@inrupt/solid-client-authn-core": "^4.0.0", - "events": "^3.3.0", - "jose": "^5.1.3", - "uuid": "^11.1.0" - } - }, - "node_modules/@inrupt/solid-client-authn-core": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@inrupt/solid-client-authn-core/-/solid-client-authn-core-4.0.0.tgz", - "integrity": "sha512-q4iur4TxEkhk9XaGAvyRP/+MjU1oBv2xlBdGE+uoXmDHAnIqUN71zZjCWZfZlyQFRETgH3OfZ9tPrNSDIPA/wg==", - "license": "MIT", - "dependencies": { - "events": "^3.3.0", - "jose": "^5.1.3", - "uuid": "^11.1.0" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || ^24.0.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3329,6 +3290,15 @@ "win32" ] }, + "node_modules/@uvdsl/solid-oidc-client-browser": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@uvdsl/solid-oidc-client-browser/-/solid-oidc-client-browser-0.2.2.tgz", + "integrity": "sha512-JhcfSPu+eVyPMl2Dz46jq9ZHZwfZSqzCrQiHkvFZyam9ZEGXmLF1QJs4O+MddiEJaF5rVeEPd20YWprp5drLKw==", + "license": "MIT", + "dependencies": { + "jose": "^5.9.6" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "dev": true, @@ -7722,15 +7692,6 @@ "license": "ISC", "peer": true }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/keyv": { "version": "4.5.4", "dev": true, @@ -8278,18 +8239,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oidc-client-ts": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.5.0.tgz", - "integrity": "sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==", - "license": "Apache-2.0", - "dependencies": { - "jwt-decode": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/once": { "version": "1.4.0", "dev": true, @@ -10589,19 +10538,6 @@ "license": "MIT", "peer": true }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", diff --git a/package.json b/package.json index 885be6f..dca2db1 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "build-dist": "webpack --progress", "postbuild-js": "rm -f dist/versionInfo.d.ts dist/versionInfo.d.ts.map", "lint": "eslint", + "lint-fix": "eslint --fix", "typecheck": "tsc --noEmit", "typecheck-test": "tsc --noEmit -p tsconfig.test.json", "test": "jest --no-coverage", @@ -72,7 +73,7 @@ "webpack-cli": "^7.0.2" }, "dependencies": { - "@inrupt/solid-client-authn-browser": "^4.0.0", + "@uvdsl/solid-oidc-client-browser": "^0.2.2", "solid-namespace": "^0.5.4" }, "peerDependencies": { diff --git a/src/authSession/authSession.ts b/src/authSession/authSession.ts index a125a97..fd8b9e3 100644 --- a/src/authSession/authSession.ts +++ b/src/authSession/authSession.ts @@ -1,7 +1,194 @@ import { - Session, -} from '@inrupt/solid-client-authn-browser' + Session as WebSession, +} from '@uvdsl/solid-oidc-client-browser' +import { + SessionCore, +} from '@uvdsl/solid-oidc-client-browser/core' +import type { Session as OidcSession, SessionDatabase } from '@uvdsl/solid-oidc-client-browser/core' + +type LegacyEventName = 'login' | 'logout' | 'sessionRestore' +type LegacyEventHandler = (...args: unknown[]) => void + +/** + * Minimal EventEmitter-style shim so that existing consumers using + * `authSession.events.on('login' | 'logout' | 'sessionRestore', handler)` + * continue working without modification. + * + * Events are emitted by SolidAuthnLogic.checkUser() (login/sessionRestore) + * and by the sessionStateChange listener below (logout). + */ +export class SessionEvents { + private readonly listeners: Map> = new Map() + + on (event: LegacyEventName, handler: LegacyEventHandler): void { + if (!this.listeners.has(event)) this.listeners.set(event, new Set()) + this.listeners.get(event)!.add(handler) + } + + off (event: LegacyEventName, handler: LegacyEventHandler): void { + this.listeners.get(event)?.delete(handler) + } + + emit (event: LegacyEventName, ...args: unknown[]): void { + this.listeners.get(event)?.forEach(h => h(...args)) + } +} + +type SessionCompatibilityShape = { + webId?: string + isActive?: boolean + info?: { + webId?: string + isLoggedIn?: boolean + } + fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise + authFetch?: (input: string | URL | Request, init?: RequestInit, dpopPayload?: any) => Promise +} + +export type SessionWithLegacyEvents = OidcSession & SessionCompatibilityShape & { events: SessionEvents } + +class MemorySessionDatabase implements SessionDatabase { + private readonly map = new Map() + + async init (): Promise { + return this + } + + async setItem (id: string, value: any): Promise { + this.map.set(id, value) + } + + async getItem (id: string): Promise { + return this.map.has(id) ? this.map.get(id) : null + } + + async deleteItem (id: string): Promise { + this.map.delete(id) + } + + async clear (): Promise { + this.map.clear() + } + + close (): void { + // No-op for in-memory database + } +} + +class IndexedDbSessionDatabase implements SessionDatabase { + private db: IDBDatabase | null = null + private readonly dbName = 'soidc' + private readonly storeName = 'session' + private readonly dbVersion = 1 + + async init (): Promise { + if (this.db) return this + + await new Promise((resolve, reject) => { + const request = indexedDB.open(this.dbName, this.dbVersion) + + request.onerror = () => reject(request.error) + request.onsuccess = () => { + this.db = request.result + resolve() + } + request.onupgradeneeded = () => { + const db = request.result + if (!db.objectStoreNames.contains(this.storeName)) { + db.createObjectStore(this.storeName) + } + } + }) + + return this + } + + async setItem (id: string, value: any): Promise { + await this.init() + await this.withStore('readwrite', store => store.put(value, id)) + } + + async getItem (id: string): Promise { + await this.init() + return this.withStore('readonly', store => store.get(id)) + } + + async deleteItem (id: string): Promise { + await this.init() + await this.withStore('readwrite', store => store.delete(id)) + } + + async clear (): Promise { + await this.init() + await this.withStore('readwrite', store => store.clear()) + } + + close (): void { + if (this.db) { + this.db.close() + this.db = null + } + } + + private withStore(mode: IDBTransactionMode, op: (store: IDBObjectStore) => IDBRequest): Promise { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('Session database not initialized')) + return + } + + const tx = this.db.transaction(this.storeName, mode) + const store = tx.objectStore(this.storeName) + const request = op(store) + + request.onerror = () => reject(request.error) + request.onsuccess = () => resolve(request.result ?? null) + }) + } +} + +function createSession (): OidcSession { + const shouldSkipWorkerInLocalDev = typeof window !== 'undefined' && + window.location.protocol === 'http:' && + /^(localhost|127\.0\.0\.1)$/.test(window.location.hostname) + + if (shouldSkipWorkerInLocalDev) { + return new SessionCore(undefined, { database: new IndexedDbSessionDatabase() }) + } + + try { + return new WebSession() + } catch (error) { + // In some deployments, worker URL resolution can become file:// and fail cross-origin. + // Fall back to SessionCore so auth still works without background refresh worker. + // Use IndexedDB to keep refresh-token persistence across page reloads. + console.warn('solid-logic: falling back to non-worker auth session:', error) + try { + return new SessionCore(undefined, { database: new IndexedDbSessionDatabase() }) + } catch (dbError) { + console.warn('solid-logic: IndexedDB unavailable, using in-memory session database:', dbError) + return new SessionCore(undefined, { database: new MemorySessionDatabase() }) + } + } +} + +const _session = createSession() +const events = new SessionEvents() + +// Emit the legacy 'logout' event when the session transitions from active to inactive. +// 'login' and 'sessionRestore' are emitted in SolidAuthnLogic.checkUser() +// because only that call site knows which path activated the session. +let _wasActive = false +if (typeof (_session as unknown as EventTarget).addEventListener === 'function') { + ;(_session as unknown as EventTarget).addEventListener('sessionStateChange', () => { + const isNowActive = (_session as any).isActive ?? Boolean((_session as any).webId) + if (_wasActive && !isNowActive) { + events.emit('logout') + } + _wasActive = isNowActive + }) +} -export const authSession = new Session() +export const authSession: SessionWithLegacyEvents = Object.assign(_session, { events }) \ No newline at end of file diff --git a/src/authn/SolidAuthnLogic.ts b/src/authn/SolidAuthnLogic.ts index 6d49a8e..1556ba3 100644 --- a/src/authn/SolidAuthnLogic.ts +++ b/src/authn/SolidAuthnLogic.ts @@ -1,26 +1,29 @@ import { namedNode, NamedNode, sym } from 'rdflib' import { appContext, offlineTestID } from './authUtil' import * as debug from '../util/debug' -import { EVENTS, Session } from '@inrupt/solid-client-authn-browser' -import { AuthenticationContext, AuthnLogic } from '../types' +import type { SessionWithLegacyEvents } from '../authSession/authSession' +import type { AuthenticationContext, AuthnLogic } from '../types' export class SolidAuthnLogic implements AuthnLogic { - private session: Session + private session: SessionWithLegacyEvents - constructor(solidAuthSession: Session) { + constructor(solidAuthSession: SessionWithLegacyEvents) { this.session = solidAuthSession } // we created authSession getter because we want to access it as authn.authSession externally - get authSession():Session { return this.session } + get authSession(): SessionWithLegacyEvents { return this.session } currentUser(): NamedNode | null { const app = appContext() if (app.viewingNoAuthPage) { return sym(app.webId) } - if (this && this.session && this.session.info && this.session.info.webId && this.session.info.isLoggedIn) { - return sym(this.session.info.webId) + const sessionAny = this.session as any + const webId = sessionAny?.info?.webId || sessionAny?.webId + const isLoggedIn = sessionAny?.info?.isLoggedIn ?? sessionAny?.isActive ?? Boolean(webId) + if (this && this.session && webId && isLoggedIn) { + return sym(webId) } return offlineTestID() // null unless testing } @@ -40,21 +43,51 @@ export class SolidAuthnLogic implements AuthnLogic { if (preLoginRedirectHash) { window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash) } - this.session.events.on(EVENTS.SESSION_RESTORED, (url) => { - debug.log(`Session restored to ${url}`) - if (document.location.toString() !== url) history.replaceState(null, '', url) - }) + const sessionAny = this.session as any + if (typeof sessionAny?.events?.on === 'function') { + // Backward-compatible hook for auth clients exposing an EventEmitter-style API. + sessionAny.events.on('sessionRestore', (url: string) => { + debug.log(`Session restored to ${url}`) + if (document.location.toString() !== url) history.replaceState(null, '', url) + }) + } /** * Handle a successful authentication redirect */ const redirectUrl = new URL(window.location.href) redirectUrl.hash = '' - await this.session - .handleIncomingRedirect({ + if (typeof sessionAny?.handleIncomingRedirect === 'function') { + await sessionAny.handleIncomingRedirect({ restorePreviousSession: true, url: redirectUrl.href }) + } else { + if (typeof sessionAny?.restore === 'function') { + const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId) + try { + await sessionAny.restore() + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + if (!/no session to restore/i.test(message)) { + throw error + } + debug.log('No previous session to restore') + } + const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId) + if (!wasActive && isNowActive) { + sessionAny.events?.emit('sessionRestore', window.location.href) + } + } + if (typeof sessionAny?.handleRedirectFromLogin === 'function') { + const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId) + await sessionAny.handleRedirectFromLogin() + const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId) + if (!wasActive && isNowActive) { + sessionAny.events?.emit('login') + } + } + } // Check to see if a hash was stored in local storage const postLoginRedirectHash = window.localStorage.getItem('preLoginRedirectHash') @@ -81,7 +114,7 @@ export class SolidAuthnLogic implements AuthnLogic { return Promise.resolve(setUserCallback ? setUserCallback(me) : me) } - const webId = this.webIdFromSession(this.session.info) + const webId = this.webIdFromSession(sessionAny?.info || sessionAny) if (webId) { me = this.saveUser(webId) } @@ -119,8 +152,17 @@ export class SolidAuthnLogic implements AuthnLogic { /** * @returns {Promise} Resolves with WebID URI or null */ - webIdFromSession (session?: { webId?: string, isLoggedIn: boolean }): string | null { - const webId = session?.webId && session.isLoggedIn ? session.webId : null + webIdFromSession (session?: { webId?: string, isLoggedIn?: boolean, isActive?: boolean }): string | null { + const webId = session?.webId + if (!webId) { + return null + } + if (typeof session?.isLoggedIn === 'boolean') { + return session.isLoggedIn ? webId : null + } + if (typeof session?.isActive === 'boolean') { + return session.isActive ? webId : null + } return webId } diff --git a/src/logic/solidLogic.ts b/src/logic/solidLogic.ts index 9c62391..11621d1 100644 --- a/src/logic/solidLogic.ts +++ b/src/logic/solidLogic.ts @@ -1,15 +1,15 @@ -import { Session } from '@inrupt/solid-client-authn-browser' import * as rdf from 'rdflib' import { LiveStore, NamedNode, Statement } from 'rdflib' import { createAclLogic } from '../acl/aclLogic' import { SolidAuthnLogic } from '../authn/SolidAuthnLogic' +import type { SessionWithLegacyEvents } from '../authSession/authSession' import { createChatLogic } from '../chat/chatLogic' import { createInboxLogic } from '../inbox/inboxLogic' import { createProfileLogic } from '../profile/profileLogic' import { createTypeIndexLogic } from '../typeIndex/typeIndexLogic' import { createContainerLogic } from '../util/containerLogic' import { createUtilityLogic } from '../util/utilityLogic' -import { AuthnLogic, SolidLogic } from '../types' +import type { AuthnLogic, SolidLogic } from '../types' import * as debug from '../util/debug' /* ** It is important to distinquish `fetch`, a function provided by the browser @@ -17,7 +17,7 @@ import * as debug from '../util/debug' ** into a `ConnectedStore` or a `LiveStore`. A Fetcher object is ** available at store.fetcher, and `fetch` function at `store.fetcher._fetch`, */ -export function createSolidLogic(specialFetch: { fetch: (url: any, requestInit: any) => any }, session: Session): SolidLogic { +export function createSolidLogic(specialFetch: { fetch: (url: any, requestInit: any) => any }, session: SessionWithLegacyEvents): SolidLogic { debug.log('SolidLogic: Unique instance created. There should only be one of these.') const store: LiveStore = rdf.graph() as LiveStore diff --git a/src/logic/solidLogicSingleton.ts b/src/logic/solidLogicSingleton.ts index fed3e23..8320b6f 100644 --- a/src/logic/solidLogicSingleton.ts +++ b/src/logic/solidLogicSingleton.ts @@ -5,9 +5,17 @@ import { SolidLogic } from '../types' const _fetch = async (url, requestInit) => { const omitCreds = requestInit && requestInit.credentials && requestInit.credentials == 'omit' - if (authSession.info.webId && !omitCreds) { // see https://github.com/solidos/solidos/issues/114 + const sessionAny = authSession as any + const sessionWebId = sessionAny?.info?.webId || sessionAny?.webId + if (sessionWebId && !omitCreds) { // see https://github.com/solidos/solidos/issues/114 // In fact fetch should respect credentials omit itself - return authSession.fetch(url, requestInit) + const authenticatedFetch = (typeof sessionAny.fetch === 'function') + ? sessionAny.fetch.bind(sessionAny) + : (typeof sessionAny.authFetch === 'function' ? sessionAny.authFetch.bind(sessionAny) : null) + if (authenticatedFetch) { + return authenticatedFetch(url, requestInit) + } + return window.fetch(url, requestInit) } else { return window.fetch(url, requestInit) } diff --git a/src/types.ts b/src/types.ts index 62a6585..8faa83a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { Session } from '@inrupt/solid-client-authn-browser' +import type { SessionWithLegacyEvents } from './authSession/authSession' import { LiveStore, NamedNode, Statement } from 'rdflib' export type AppDetails = { @@ -21,7 +21,7 @@ export type AuthenticationContext = { } export interface AuthnLogic { - authSession: Session //this needs to be deprecated in the future. Is only here to allow imports like panes.UI.authn.authSession prior to moving authn from ui to logic + authSession: SessionWithLegacyEvents //this needs to be deprecated in the future. Is only here to allow imports like panes.UI.authn.authSession prior to moving authn from ui to logic currentUser: () => NamedNode | null checkUser: (setUserCallback?: (me: NamedNode | null) => T) => Promise saveUser: (webId: NamedNode | string | null, diff --git a/test/logic.test.ts b/test/logic.test.ts index 5cdb548..e406f92 100644 --- a/test/logic.test.ts +++ b/test/logic.test.ts @@ -1,4 +1,6 @@ import { solidLogicSingleton } from '../src/logic/solidLogicSingleton' +import { authSession } from '../src/authSession/authSession' +import fetchMock from 'jest-fetch-mock' import { silenceDebugMessages } from './helpers/debugger' silenceDebugMessages() @@ -27,3 +29,58 @@ describe('authn', () => { }) }) +describe('solidLogicSingleton fetch bridge', () => { + const singletonFetch = (solidLogicSingleton.store.fetcher as any)._fetch as (url: string, init?: RequestInit) => Promise + + let originalFetch: any + let originalAuthFetch: any + let originalWebId: any + let originalInfo: any + + beforeEach(() => { + fetchMock.resetMocks() + + const sessionAny = authSession as any + originalFetch = sessionAny.fetch + originalAuthFetch = sessionAny.authFetch + originalWebId = sessionAny.webId + originalInfo = sessionAny.info + + sessionAny.webId = undefined + sessionAny.info = { isLoggedIn: false } + }) + + afterEach(() => { + const sessionAny = authSession as any + sessionAny.fetch = originalFetch + sessionAny.authFetch = originalAuthFetch + sessionAny.webId = originalWebId + sessionAny.info = originalInfo + }) + + it('uses window.fetch when credentials are omit even if a session exists', async () => { + const sessionAny = authSession as any + sessionAny.webId = 'https://alice.example/profile#me' + sessionAny.fetch = jest.fn().mockResolvedValue(new Response('session')) + + fetchMock.mockResponseOnce('window') + + await singletonFetch('https://example.com/resource', { credentials: 'omit' }) + + expect(sessionAny.fetch).not.toHaveBeenCalled() + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('falls back to authFetch when session.fetch is unavailable', async () => { + const sessionAny = authSession as any + sessionAny.webId = 'https://alice.example/profile#me' + sessionAny.fetch = undefined + sessionAny.authFetch = jest.fn().mockResolvedValue(new Response('auth')) + + await singletonFetch('https://example.com/resource') + + expect(sessionAny.authFetch).toHaveBeenCalledTimes(1) + expect(fetchMock).not.toHaveBeenCalled() + }) +}) + diff --git a/test/mocks/solid-oidc-client-browser.ts b/test/mocks/solid-oidc-client-browser.ts new file mode 100644 index 0000000..76668a0 --- /dev/null +++ b/test/mocks/solid-oidc-client-browser.ts @@ -0,0 +1,85 @@ +type Listener = (...args: any[]) => void + +class EventEmitterLike { + private listeners: Record = {} + + on(event: string, listener: Listener): void { + const list = this.listeners[event] || [] + list.push(listener) + this.listeners[event] = list + } + + emit(event: string, ...args: any[]): void { + const list = this.listeners[event] || [] + list.forEach(listener => listener(...args)) + } +} + +export class Session { + info: { webId?: string, isLoggedIn: boolean } = { isLoggedIn: false } + webId?: string + isActive = false + events = new EventEmitterLike() + + async handleIncomingRedirect(): Promise { + return + } + + async handleRedirectFromLogin(): Promise { + return + } + + async restore(): Promise { + return + } + + async login(): Promise { + return + } + + async logout(): Promise { + this.info = { isLoggedIn: false } + this.webId = undefined + this.isActive = false + } + + fetch(input: RequestInfo | URL, init?: RequestInit): Promise { + return globalThis.fetch(input, init) + } + + authFetch(input: RequestInfo | URL, init?: RequestInit): Promise { + return globalThis.fetch(input, init) + } +} + +export class SessionCore extends Session { + constructor(_clientDetails?: unknown, _sessionOptions?: unknown) { + super() + } +} + +export class SessionIDB { + async init(): Promise { + return this + } + + async setItem(_id: string, _value: any): Promise { + return + } + + async getItem(_id: string): Promise { + return null + } + + async deleteItem(_id: string): Promise { + return + } + + async clear(): Promise { + return + } + + close(): void { + return + } +} diff --git a/test/solidAuthLogic.test.ts b/test/solidAuthLogic.test.ts index 4d1d633..b0132a2 100644 --- a/test/solidAuthLogic.test.ts +++ b/test/solidAuthLogic.test.ts @@ -10,6 +10,20 @@ import { AuthenticationContext } from '../src/types' silenceDebugMessages() let solidAuthnLogic +jest.mock('../src/authSession/authSession', () => { + const EventEmitter = require('events') + const authSession = { + events: new EventEmitter(), + addEventListener: function (event, listener) { + this.events.on(event, listener) + }, + removeEventListener: function (event, listener) { + this.events.off(event, listener) + }, + } + return { authSession } +}) + describe('SolidAuthnLogic', () => { beforeEach(() => {