125 lines
3.6 KiB
JavaScript
125 lines
3.6 KiB
JavaScript
/**
|
|
* Creates the skeleton-owned side of an external session integration.
|
|
* Credential acquisition and storage stay inside the supplied external owner.
|
|
*
|
|
* @param {{
|
|
* readState(): import("../../application/ports/auth-session-port.js").SessionState,
|
|
* subscribe(listener: () => void): () => void,
|
|
* beginSignIn(returnTo?: string): Promise<void>,
|
|
* signOut(): Promise<void>,
|
|
* attachCredential(request: Request): Promise<Request>,
|
|
* recoverSession(): Promise<"restored" | "no-session">,
|
|
* notifyUnauthenticated(): void
|
|
* }} owner
|
|
* @returns {import("../../application/ports/auth-session-port.js").AuthSessionPort}
|
|
*/
|
|
export function createExternalAuthSessionAdapter(owner) {
|
|
return Object.freeze({
|
|
getState() {
|
|
return owner.readState();
|
|
},
|
|
subscribe(listener) {
|
|
return owner.subscribe(listener);
|
|
},
|
|
async beginSignIn(returnTo) {
|
|
await owner.beginSignIn(returnTo);
|
|
},
|
|
async signOut() {
|
|
await owner.signOut();
|
|
},
|
|
/** @param {Request} request */
|
|
async attach(request) {
|
|
const attached = await owner.attachCredential(request);
|
|
if (!(attached instanceof Request)) {
|
|
throw new TypeError("Auth owner returned an invalid request");
|
|
}
|
|
return attached;
|
|
},
|
|
async recover() {
|
|
const result = await owner.recoverSession();
|
|
if (result !== "restored" && result !== "no-session") {
|
|
throw new TypeError("Auth owner returned an invalid recovery state");
|
|
}
|
|
return result;
|
|
},
|
|
onUnauthenticated() {
|
|
owner.notifyUnauthenticated();
|
|
},
|
|
});
|
|
}
|
|
|
|
export function createAnonymousSessionAdapter() {
|
|
return createExternalAuthSessionAdapter({
|
|
readState: () => "unauthenticated",
|
|
subscribe: () => () => {},
|
|
beginSignIn: async () => {},
|
|
signOut: async () => {},
|
|
attachCredential: async (request) => request,
|
|
recoverSession: async () => "no-session",
|
|
notifyUnauthenticated: () => {},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Local/test-only session seam. It never creates or stores credentials.
|
|
*
|
|
* @param {import("../../application/ports/auth-session-port.js").SessionState} [initialState]
|
|
*/
|
|
export function createDemoSessionAdapter(initialState = "unauthenticated") {
|
|
let state = initialState;
|
|
const listeners = new Set();
|
|
|
|
function notify() {
|
|
for (const listener of listeners) listener();
|
|
}
|
|
|
|
/** @param {import("../../application/ports/auth-session-port.js").SessionState} next */
|
|
function setState(next) {
|
|
state = next;
|
|
notify();
|
|
}
|
|
|
|
return Object.freeze({
|
|
getState: () => state,
|
|
/** @param {() => void} listener */
|
|
subscribe(listener) {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
},
|
|
async beginSignIn() {
|
|
setState("authenticated");
|
|
},
|
|
async signOut() {
|
|
setState("unauthenticated");
|
|
},
|
|
/** @param {Request} request */
|
|
async attach(request) {
|
|
return request;
|
|
},
|
|
async recover() {
|
|
if (state === "recovery-pending") {
|
|
setState("authenticated");
|
|
return /** @type {const} */ ("restored");
|
|
}
|
|
return /** @type {const} */ ("no-session");
|
|
},
|
|
onUnauthenticated() {
|
|
setState("unauthenticated");
|
|
},
|
|
setState,
|
|
});
|
|
}
|
|
|
|
export function createUnavailableSessionAdapter() {
|
|
return Object.freeze({
|
|
getState: () => /** @type {const} */ ("integration-failed"),
|
|
subscribe: () => () => {},
|
|
beginSignIn: async () => {},
|
|
signOut: async () => {},
|
|
/** @param {Request} request */
|
|
attach: async (request) => request,
|
|
recover: async () => /** @type {const} */ ("no-session"),
|
|
onUnauthenticated: () => {},
|
|
});
|
|
}
|