From 62ee41b2df1fa711db40fc428368c3b57dd108e3 Mon Sep 17 00:00:00 2001 From: m5x5 Date: Mon, 18 May 2026 20:58:22 +0200 Subject: [PATCH 1/3] feat: swap @inrupt/solid-client-authn-browser for @uvdsl/solid-oidc-client-browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps uvdsl's default WebWorkerSession in a thin Inrupt-shape adapter inside authSession.ts so downstream call sites (panes, solid-ui) keep working unchanged. The SharedWorker provides background token refresh and cross-tab token sharing — the intended uvdsl usage for web apps. Refs SolidOS/solid-logic#274 Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- src/authSession/authSession.ts | 60 +++++++++++++++++++++++++++++++--- src/authn/SolidAuthnLogic.ts | 2 +- src/logic/solidLogic.ts | 2 +- src/types.ts | 2 +- webpack.config.mjs | 8 ++++- 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 885be6f..c311a3a 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,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..b5f973a 100644 --- a/src/authSession/authSession.ts +++ b/src/authSession/authSession.ts @@ -1,7 +1,57 @@ -import { - Session, -} from '@inrupt/solid-client-authn-browser' +import { Session as UvdslSession, SessionIDB } from '@uvdsl/solid-oidc-client-browser' -export const authSession = new Session() +/** Inrupt-shape `Session` API consumed by solid-logic + downstream panes, + * backed by uvdsl's default `Session` (WebWorkerSession). */ + +export const EVENTS = { LOGIN: 'login', LOGOUT: 'logout', SESSION_RESTORED: 'sessionRestore' } as const +type Cb = (...args: any[]) => void + +export class Session { + private inner: UvdslSession | null = null + private subs: Record> = {} + readonly events = { + on: (n: string, cb: Cb) => { (this.subs[n] ||= new Set()).add(cb) }, + off: (n: string, cb: Cb) => this.subs[n]?.delete(cb), + removeListener: (n: string, cb: Cb) => this.subs[n]?.delete(cb), + } + private emit(n: string, ...a: any[]) { + this.subs[n]?.forEach(cb => { try { cb(...a) } catch (_) {} }) + } + private ensure(redirect?: string) { + return this.inner ||= new UvdslSession( + { client_name: 'SolidOS', redirect_uris: [redirect || location.origin + location.pathname] } as any, + { database: new SessionIDB() } as any, + ) + } + + get info() { + const s = this.inner + return { webId: s?.isActive ? s.webId : undefined, isLoggedIn: !!s?.isActive } + } + + fetch = (input: RequestInfo | URL, init?: RequestInit) => + this.ensure().authFetch(input as any, init) - \ No newline at end of file + async login(opts: { oidcIssuer: string; redirectUrl?: string }) { + const r = opts.redirectUrl || location.href + await this.ensure(r).login(opts.oidcIssuer, r) + } + + async logout() { await this.ensure().logout(); this.emit('logout') } + + async handleIncomingRedirect(opts?: { restorePreviousSession?: boolean; url?: string }) { + const url = new URL(opts?.url || location.href) + const s = this.ensure() + if (url.searchParams.has('code') && url.searchParams.has('state')) { + await s.handleRedirectFromLogin() + for (const k of ['code', 'state', 'iss']) url.searchParams.delete(k) + try { history.replaceState(null, '', url.toString()) } catch (_) {} + if (s.isActive) this.emit('login') + } else if (opts?.restorePreviousSession !== false) { + await s.restore() + if (s.isActive) this.emit('sessionRestore', url.toString()) + } + } +} + +export const authSession = new Session() diff --git a/src/authn/SolidAuthnLogic.ts b/src/authn/SolidAuthnLogic.ts index 6d49a8e..996b393 100644 --- a/src/authn/SolidAuthnLogic.ts +++ b/src/authn/SolidAuthnLogic.ts @@ -1,7 +1,7 @@ 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 { EVENTS, Session } from '../authSession/authSession' import { AuthenticationContext, AuthnLogic } from '../types' export class SolidAuthnLogic implements AuthnLogic { diff --git a/src/logic/solidLogic.ts b/src/logic/solidLogic.ts index 9c62391..c9eb112 100644 --- a/src/logic/solidLogic.ts +++ b/src/logic/solidLogic.ts @@ -1,4 +1,4 @@ -import { Session } from '@inrupt/solid-client-authn-browser' +import { Session } from '../authSession/authSession' import * as rdf from 'rdflib' import { LiveStore, NamedNode, Statement } from 'rdflib' import { createAclLogic } from '../acl/aclLogic' diff --git a/src/types.ts b/src/types.ts index 62a6585..d30d5dc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { Session } from '@inrupt/solid-client-authn-browser' +import { Session } from './authSession/authSession' import { LiveStore, NamedNode, Statement } from 'rdflib' export type AppDetails = { diff --git a/webpack.config.mjs b/webpack.config.mjs index dc61bd7..879793e 100644 --- a/webpack.config.mjs +++ b/webpack.config.mjs @@ -10,7 +10,13 @@ const externalsBase = { '@trust/webcrypto': 'crypto', '@xmldom/xmldom': 'window', 'whatwg-url': 'URL', - 'rdflib': '$rdf' + 'rdflib': '$rdf', + // Must externalize: uvdsl's SharedWorker URL is constructed via + // `new URL('./RefreshWorker.js', import.meta.url)`. If bundled inline here, + // webpack bakes a `file://…/solid-logic/dist/…` path that browsers refuse + // to load over http:// origin. Leaving it external lets the consumer bundle + // (mashlib) resolve the worker URL relative to its own publicPath. + '@uvdsl/solid-oidc-client-browser': '@uvdsl/solid-oidc-client-browser' } const externalsESM = { From e394fb9093407bc0af2056767727d337c9765e83 Mon Sep 17 00:00:00 2001 From: m5x5 Date: Mon, 18 May 2026 21:04:41 +0200 Subject: [PATCH 2/3] chore: regenerate package-lock.json for uvdsl swap --- package-lock.json | 84 ++++++----------------------------------------- 1 file changed, 10 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a7d458..72398aa 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", From d9b5a4e142b7c20f1266e3e5b7a5571a6d68c4a2 Mon Sep 17 00:00:00 2001 From: m5x5 Date: Mon, 18 May 2026 21:07:17 +0200 Subject: [PATCH 3/3] test: mock @uvdsl/solid-oidc-client-browser (uses import.meta.url, not parseable by jest/jsdom) --- test/helpers/setup.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/helpers/setup.ts b/test/helpers/setup.ts index 257880f..16f51d8 100644 --- a/test/helpers/setup.ts +++ b/test/helpers/setup.ts @@ -4,4 +4,21 @@ import { TextEncoder, TextDecoder } from 'util' global.TextEncoder = TextEncoder as any global.TextDecoder = TextDecoder as any -fetchMock.enableMocks() \ No newline at end of file +fetchMock.enableMocks() + +// @uvdsl/solid-oidc-client-browser uses `import.meta.url` (for its +// SharedWorker URL), which jest/jsdom cannot evaluate. Stub the module +// surface used by the adapter so SolidAuthnLogic can be unit-tested. +jest.mock('@uvdsl/solid-oidc-client-browser', () => { + class Session { + isActive = false + webId = undefined + async login() {} + async logout() {} + async handleRedirectFromLogin() {} + async restore() {} + authFetch(input: any, init?: any) { return fetch(input, init) } + } + class SessionIDB {} + return { Session, SessionIDB } +}, { virtual: true }) \ No newline at end of file