Skip to content

Commit fc90d8d

Browse files
committed
replace Inrupt by Uvdsl OIDC client
1 parent b8508f1 commit fc90d8d

10 files changed

Lines changed: 201 additions & 98 deletions

File tree

jest.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export default {
1111
transform: {
1212
'^.+\\.[tj]sx?$': ['babel-jest', { configFile: './babel.config.mjs' }],
1313
},
14+
moduleNameMapper: {
15+
'^@uvdsl/solid-oidc-client-browser$': '<rootDir>/test/mocks/solid-oidc-client-browser.ts',
16+
},
1417
setupFilesAfterEnv: ['./test/helpers/setup.ts'],
1518
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
1619
roots: ['<rootDir>/src', '<rootDir>/test'],

package-lock.json

Lines changed: 10 additions & 74 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"webpack-cli": "^7.0.2"
7373
},
7474
"dependencies": {
75-
"@inrupt/solid-client-authn-browser": "^4.0.0",
75+
"@uvdsl/solid-oidc-client-browser": "^0.2.2",
7676
"solid-namespace": "^0.5.4"
7777
},
7878
"peerDependencies": {

src/authSession/authSession.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,54 @@
11
import {
22
Session,
3-
} from '@inrupt/solid-client-authn-browser'
3+
} from '@uvdsl/solid-oidc-client-browser'
44

5-
export const authSession = new Session()
5+
type LegacyEventName = 'login' | 'logout' | 'sessionRestore'
6+
type LegacyEventHandler = (...args: unknown[]) => void
7+
8+
/**
9+
* Minimal EventEmitter-style shim so that existing consumers using
10+
* `authSession.events.on('login' | 'logout' | 'sessionRestore', handler)`
11+
* continue working without modification.
12+
*
13+
* Events are emitted by SolidAuthnLogic.checkUser() (login/sessionRestore)
14+
* and by the sessionStateChange listener below (logout).
15+
*/
16+
export class SessionEvents {
17+
private readonly listeners: Map<string, Set<LegacyEventHandler>> = new Map()
18+
19+
on (event: LegacyEventName, handler: LegacyEventHandler): void {
20+
if (!this.listeners.has(event)) this.listeners.set(event, new Set())
21+
this.listeners.get(event)!.add(handler)
22+
}
23+
24+
off (event: LegacyEventName, handler: LegacyEventHandler): void {
25+
this.listeners.get(event)?.delete(handler)
26+
}
27+
28+
emit (event: LegacyEventName, ...args: unknown[]): void {
29+
this.listeners.get(event)?.forEach(h => h(...args))
30+
}
31+
}
32+
33+
export type SessionWithLegacyEvents = Session & { events: SessionEvents }
34+
35+
const _session = new Session()
36+
const events = new SessionEvents()
37+
38+
// Emit the legacy 'logout' event when the session transitions from active to inactive.
39+
// 'login' and 'sessionRestore' are emitted in SolidAuthnLogic.checkUser()
40+
// because only that call site knows which path activated the session.
41+
let _wasActive = false
42+
if (typeof (_session as unknown as EventTarget).addEventListener === 'function') {
43+
;(_session as unknown as EventTarget).addEventListener('sessionStateChange', () => {
44+
const isNowActive = (_session as any).isActive ?? Boolean((_session as any).webId)
45+
if (_wasActive && !isNowActive) {
46+
events.emit('logout')
47+
}
48+
_wasActive = isNowActive
49+
})
50+
}
51+
52+
export const authSession: SessionWithLegacyEvents = Object.assign(_session, { events })
653

754

src/authn/SolidAuthnLogic.ts

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
import { namedNode, NamedNode, sym } from 'rdflib'
22
import { appContext, offlineTestID } from './authUtil'
33
import * as debug from '../util/debug'
4-
import { EVENTS, Session } from '@inrupt/solid-client-authn-browser'
4+
import { SessionWithLegacyEvents } from '../authSession/authSession'
55
import { AuthenticationContext, AuthnLogic } from '../types'
66

77
export class SolidAuthnLogic implements AuthnLogic {
8-
private session: Session
8+
private session: SessionWithLegacyEvents
99

10-
constructor(solidAuthSession: Session) {
10+
constructor(solidAuthSession: SessionWithLegacyEvents) {
1111
this.session = solidAuthSession
1212
}
1313

1414
// we created authSession getter because we want to access it as authn.authSession externally
15-
get authSession():Session { return this.session }
15+
get authSession(): SessionWithLegacyEvents { return this.session }
1616

1717
currentUser(): NamedNode | null {
1818
const app = appContext()
1919
if (app.viewingNoAuthPage) {
2020
return sym(app.webId)
2121
}
22-
if (this && this.session && this.session.info && this.session.info.webId && this.session.info.isLoggedIn) {
23-
return sym(this.session.info.webId)
22+
const sessionAny = this.session as any
23+
const webId = sessionAny?.info?.webId || sessionAny?.webId
24+
const isLoggedIn = sessionAny?.info?.isLoggedIn ?? sessionAny?.isActive ?? Boolean(webId)
25+
if (this && this.session && webId && isLoggedIn) {
26+
return sym(webId)
2427
}
2528
return offlineTestID() // null unless testing
2629
}
@@ -40,21 +43,51 @@ export class SolidAuthnLogic implements AuthnLogic {
4043
if (preLoginRedirectHash) {
4144
window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash)
4245
}
43-
this.session.events.on(EVENTS.SESSION_RESTORED, (url) => {
44-
debug.log(`Session restored to ${url}`)
45-
if (document.location.toString() !== url) history.replaceState(null, '', url)
46-
})
46+
const sessionAny = this.session as any
47+
if (typeof sessionAny?.events?.on === 'function') {
48+
// Backward-compatible hook for auth clients exposing an EventEmitter-style API.
49+
sessionAny.events.on('sessionRestore', (url: string) => {
50+
debug.log(`Session restored to ${url}`)
51+
if (document.location.toString() !== url) history.replaceState(null, '', url)
52+
})
53+
}
4754

4855
/**
4956
* Handle a successful authentication redirect
5057
*/
5158
const redirectUrl = new URL(window.location.href)
5259
redirectUrl.hash = ''
53-
await this.session
54-
.handleIncomingRedirect({
60+
if (typeof sessionAny?.handleIncomingRedirect === 'function') {
61+
await sessionAny.handleIncomingRedirect({
5562
restorePreviousSession: true,
5663
url: redirectUrl.href
5764
})
65+
} else {
66+
if (typeof sessionAny?.restore === 'function') {
67+
const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)
68+
try {
69+
await sessionAny.restore()
70+
} catch (error) {
71+
const message = error instanceof Error ? error.message : String(error)
72+
if (!/no session to restore/i.test(message)) {
73+
throw error
74+
}
75+
debug.log('No previous session to restore')
76+
}
77+
const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)
78+
if (!wasActive && isNowActive) {
79+
sessionAny.events?.emit('sessionRestore', window.location.href)
80+
}
81+
}
82+
if (typeof sessionAny?.handleRedirectFromLogin === 'function') {
83+
const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)
84+
await sessionAny.handleRedirectFromLogin()
85+
const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)
86+
if (!wasActive && isNowActive) {
87+
sessionAny.events?.emit('login')
88+
}
89+
}
90+
}
5891

5992
// Check to see if a hash was stored in local storage
6093
const postLoginRedirectHash = window.localStorage.getItem('preLoginRedirectHash')
@@ -81,7 +114,7 @@ export class SolidAuthnLogic implements AuthnLogic {
81114
return Promise.resolve(setUserCallback ? setUserCallback(me) : me)
82115
}
83116

84-
const webId = this.webIdFromSession(this.session.info)
117+
const webId = this.webIdFromSession(sessionAny?.info || sessionAny)
85118
if (webId) {
86119
me = this.saveUser(webId)
87120
}
@@ -119,8 +152,17 @@ export class SolidAuthnLogic implements AuthnLogic {
119152
/**
120153
* @returns {Promise<string|null>} Resolves with WebID URI or null
121154
*/
122-
webIdFromSession (session?: { webId?: string, isLoggedIn: boolean }): string | null {
123-
const webId = session?.webId && session.isLoggedIn ? session.webId : null
155+
webIdFromSession (session?: { webId?: string, isLoggedIn?: boolean, isActive?: boolean }): string | null {
156+
const webId = session?.webId
157+
if (!webId) {
158+
return null
159+
}
160+
if (typeof session?.isLoggedIn === 'boolean') {
161+
return session.isLoggedIn ? webId : null
162+
}
163+
if (typeof session?.isActive === 'boolean') {
164+
return session.isActive ? webId : null
165+
}
124166
return webId
125167
}
126168

src/logic/solidLogic.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Session } from '@inrupt/solid-client-authn-browser'
21
import * as rdf from 'rdflib'
32
import { LiveStore, NamedNode, Statement } from 'rdflib'
43
import { createAclLogic } from '../acl/aclLogic'
54
import { SolidAuthnLogic } from '../authn/SolidAuthnLogic'
5+
import { SessionWithLegacyEvents } from '../authSession/authSession'
66
import { createChatLogic } from '../chat/chatLogic'
77
import { createInboxLogic } from '../inbox/inboxLogic'
88
import { createProfileLogic } from '../profile/profileLogic'
@@ -17,7 +17,7 @@ import * as debug from '../util/debug'
1717
** into a `ConnectedStore` or a `LiveStore`. A Fetcher object is
1818
** available at store.fetcher, and `fetch` function at `store.fetcher._fetch`,
1919
*/
20-
export function createSolidLogic(specialFetch: { fetch: (url: any, requestInit: any) => any }, session: Session): SolidLogic {
20+
export function createSolidLogic(specialFetch: { fetch: (url: any, requestInit: any) => any }, session: SessionWithLegacyEvents): SolidLogic {
2121

2222
debug.log('SolidLogic: Unique instance created. There should only be one of these.')
2323
const store: LiveStore = rdf.graph() as LiveStore

src/logic/solidLogicSingleton.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ import { SolidLogic } from '../types'
55

66
const _fetch = async (url, requestInit) => {
77
const omitCreds = requestInit && requestInit.credentials && requestInit.credentials == 'omit'
8-
if (authSession.info.webId && !omitCreds) { // see https://github.com/solidos/solidos/issues/114
8+
const sessionAny = authSession as any
9+
const sessionWebId = sessionAny?.info?.webId || sessionAny?.webId
10+
if (sessionWebId && !omitCreds) { // see https://github.com/solidos/solidos/issues/114
911
// In fact fetch should respect credentials omit itself
10-
return authSession.fetch(url, requestInit)
12+
const authenticatedFetch = (typeof sessionAny.fetch === 'function')
13+
? sessionAny.fetch.bind(sessionAny)
14+
: (typeof sessionAny.authFetch === 'function' ? sessionAny.authFetch.bind(sessionAny) : null)
15+
if (authenticatedFetch) {
16+
return authenticatedFetch(url, requestInit)
17+
}
18+
return window.fetch(url, requestInit)
1119
} else {
1220
return window.fetch(url, requestInit)
1321
}

src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Session } from '@inrupt/solid-client-authn-browser'
1+
import { SessionWithLegacyEvents } from './authSession/authSession'
22
import { LiveStore, NamedNode, Statement } from 'rdflib'
33

44
export type AppDetails = {
@@ -21,7 +21,7 @@ export type AuthenticationContext = {
2121
}
2222

2323
export interface AuthnLogic {
24-
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
24+
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
2525
currentUser: () => NamedNode | null
2626
checkUser: <T>(setUserCallback?: (me: NamedNode | null) => T) => Promise<NamedNode | T | null>
2727
saveUser: (webId: NamedNode | string | null,

0 commit comments

Comments
 (0)