147 lines
4.7 KiB
JavaScript
147 lines
4.7 KiB
JavaScript
import {
|
|
createDemoSessionAdapter,
|
|
createExternalAuthSessionAdapter,
|
|
createUnavailableSessionAdapter,
|
|
} from "../adapters/auth/external-session-adapter.js";
|
|
import { createHttpClient } from "../adapters/http/client.js";
|
|
import {
|
|
createQueryClient,
|
|
} from "../adapters/query-cache/tanstack-query-cache.js";
|
|
import { createBrowserStorageAdapter } from "../adapters/storage/browser-storage-adapter.js";
|
|
import { createTelemetryAdapter } from "../adapters/telemetry/best-effort-telemetry.js";
|
|
import { fetchReleaseManifest } from "./load-release-manifest.js";
|
|
import { createInstalledFeatureInputs } from "../features/installed-feature-adapters.js";
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} host
|
|
* @returns {Parameters<typeof createExternalAuthSessionAdapter>[0] | null}
|
|
*/
|
|
function externalOwnerFrom(host) {
|
|
const candidate = host.__CA_FRONTEND_AUTH_OWNER__;
|
|
if (!candidate || typeof candidate !== "object") return null;
|
|
const owner = /** @type {Record<string, unknown>} */ (candidate);
|
|
const required = [
|
|
"readState",
|
|
"subscribe",
|
|
"beginSignIn",
|
|
"signOut",
|
|
"attachCredential",
|
|
"recoverSession",
|
|
"notifyUnauthenticated",
|
|
];
|
|
return required.every((name) => typeof owner[name] === "function")
|
|
? /** @type {Parameters<typeof createExternalAuthSessionAdapter>[0]} */ (
|
|
candidate
|
|
)
|
|
: null;
|
|
}
|
|
|
|
/** @param {unknown} value */
|
|
function storageOrUndefined(value) {
|
|
return typeof Storage !== "undefined" && value instanceof Storage
|
|
? value
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* Runtime-aware transport factory. Feature gateway composition calls this
|
|
* factory when a registered API capability is installed.
|
|
*
|
|
* @param {{
|
|
* runtime: Awaited<ReturnType<typeof import("./load-runtime-config.js").loadRuntimeConfig>>,
|
|
* authSession: import("../application/ports/auth-session-port.js").AuthSessionPort,
|
|
* fetcher?: typeof fetch,
|
|
* clock?: import("../application/ports/clock-port.js").ClockPort,
|
|
* scheduler?: Parameters<typeof createHttpClient>[0]["scheduler"]
|
|
* }} context
|
|
*/
|
|
export function createRuntimeHttpClient(context, contract = {}) {
|
|
return createHttpClient({
|
|
baseUrl: context.runtime.config.API_BASE_URL,
|
|
timeoutMs: context.runtime.config.REQUEST_TIMEOUT_MS,
|
|
maxRetryAttempts: context.runtime.config.MAX_RETRY_ATTEMPTS,
|
|
authSession: context.authSession,
|
|
fetcher: context.fetcher,
|
|
clock: context.clock,
|
|
scheduler: context.scheduler,
|
|
...contract,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {{
|
|
* runtime: Awaited<ReturnType<typeof import("./load-runtime-config.js").loadRuntimeConfig>>,
|
|
* release: Awaited<ReturnType<typeof import("./load-release-manifest.js").loadReleaseManifest>>,
|
|
* host?: Record<string, unknown>,
|
|
* fetcher?: typeof fetch
|
|
* }} context
|
|
*/
|
|
export async function createRuntimeAdapters(context) {
|
|
const host = context.host ?? /** @type {Record<string, unknown>} */ (globalThis);
|
|
const config = context.runtime.config;
|
|
const externalOwner = externalOwnerFrom(host);
|
|
const authSession =
|
|
config.AUTH_MODE === "demo"
|
|
? createDemoSessionAdapter()
|
|
: externalOwner
|
|
? createExternalAuthSessionAdapter(externalOwner)
|
|
: createUnavailableSessionAdapter();
|
|
const queryClient = createQueryClient();
|
|
const storage = createBrowserStorageAdapter({
|
|
localStorage: storageOrUndefined(host.localStorage),
|
|
sessionStorage: storageOrUndefined(host.sessionStorage),
|
|
});
|
|
const telemetry = createTelemetryAdapter({
|
|
enabled: config.TELEMETRY_ENABLED,
|
|
endpoint: config.TELEMETRY_ENDPOINT,
|
|
fetcher: context.fetcher,
|
|
});
|
|
const releaseInfo = Object.freeze({
|
|
async getCurrent() {
|
|
return structuredClone(context.release);
|
|
},
|
|
async refresh() {
|
|
return fetchReleaseManifest(config.RELEASE_MANIFEST_URL, {
|
|
fetcher: context.fetcher,
|
|
buildId: context.release.buildId,
|
|
releaseId: context.release.releaseId,
|
|
});
|
|
},
|
|
});
|
|
const navigation = Object.freeze({
|
|
reload() {
|
|
const location =
|
|
/** @type {{reload?: () => void} | undefined} */ (host.location);
|
|
if (typeof location?.reload !== "function") {
|
|
throw new Error("Browser reload is unavailable");
|
|
}
|
|
location.reload();
|
|
},
|
|
});
|
|
const featureInputs = createInstalledFeatureInputs({
|
|
createHttpClient: (contract) =>
|
|
createRuntimeHttpClient(
|
|
{
|
|
runtime: context.runtime,
|
|
authSession,
|
|
fetcher: context.fetcher,
|
|
},
|
|
contract,
|
|
),
|
|
});
|
|
|
|
return Object.freeze({
|
|
outputPorts: Object.freeze({
|
|
session: authSession,
|
|
preferences: storage,
|
|
diagnostics: telemetry,
|
|
releaseInfo,
|
|
navigation,
|
|
}),
|
|
infrastructure: Object.freeze({
|
|
queryClient,
|
|
}),
|
|
featureInputs,
|
|
});
|
|
}
|