chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
@@ -0,0 +1,281 @@
import {
WEB_PUSH_LIMITS,
WEB_PUSH_PROTOCOLS,
webPushFailure,
webPushSuccess,
type WebPushObserver,
} from "../../contracts/web-push.ts";
import type { PushAssociationFenceStore } from "./push-association-fence-store.ts";
import {
createPushEventAdapter,
type PushEventFacade,
} from "./inbound/push-event-adapter.ts";
import {
createNotificationClickAdapter,
type NotificationClickEventFacade,
type WindowClientFacade,
} from "./inbound/notification-click-adapter.ts";
import type {
AssociationNotificationTagDigest,
WebPushNotificationRegistry,
} from "./notification-registry.ts";
import {
createLinkedAbortController,
nativeFailure,
observeWebPush,
withAbortableDeadline,
type TimeoutScheduler,
} from "./runtime-support.ts";
type FunctionalEventFacade = Readonly<{
waitUntil(task: Promise<void>): void;
}>;
type ServiceWorkerRegistrationFacade = Readonly<{
showNotification(
title: string,
options: Readonly<{
body: string;
data: unknown;
requireInteraction: false;
tag: string;
}>,
): Promise<void>;
}>;
type ServiceWorkerClientsFacade = Readonly<{
matchAll(input: Readonly<{
type: "window";
includeUncontrolled: boolean;
}>): Promise<readonly unknown[]>;
openWindow(url: string): Promise<unknown>;
}>;
/**
* Structural worker host used deliberately instead of exposing DOM worker
* globals to application/test compilation. A selected worker entry adapts its
* native scope to this facade; this factory has no registration side effect.
*/
export type ServiceWorkerEventHost = Readonly<{
origin: string;
registration: ServiceWorkerRegistrationFacade;
clients: ServiceWorkerClientsFacade;
addEventListener(type: string, listener: (event: unknown) => void): void;
removeEventListener(type: string, listener: (event: unknown) => void): void;
}>;
export type WebPushServiceWorkerRuntime = Readonly<{
dispose(): void;
}>;
export function createWebPushServiceWorkerRuntime(
dependencies: Readonly<{
host: ServiceWorkerEventHost;
fenceStore: PushAssociationFenceStore;
registry: WebPushNotificationRegistry;
now?: () => number;
scheduler?: TimeoutScheduler;
observer?: WebPushObserver;
tagDigest?: AssociationNotificationTagDigest;
}>,
): WebPushServiceWorkerRuntime {
const lifecycle = new AbortController();
const push = createPushEventAdapter({
fenceStore: dependencies.fenceStore,
registry: dependencies.registry,
notifications: {
showNotification: (title, options) =>
dependencies.host.registration.showNotification(title, options),
},
now: dependencies.now,
scheduler: dependencies.scheduler,
observer: dependencies.observer,
tagDigest: dependencies.tagDigest,
signal: lifecycle.signal,
});
const click = createNotificationClickAdapter({
fenceStore: dependencies.fenceStore,
registry: dependencies.registry,
clients: {
async matchControlledWindowClients() {
const candidates = await dependencies.host.clients.matchAll({
type: "window",
includeUncontrolled: false,
});
return candidates
.map(windowClientFacade)
.filter(
(candidate): candidate is WindowClientFacade =>
candidate !== null,
);
},
async openWindow(url) {
return windowClientFacade(
await dependencies.host.clients.openWindow(url),
);
},
},
origin: dependencies.host.origin,
now: dependencies.now,
scheduler: dependencies.scheduler,
observer: dependencies.observer,
signal: lifecycle.signal,
});
const onPush = (event: unknown) => {
const facade = pushEventFacade(event);
if (facade) void push.handle(facade);
};
const onNotificationClick = (event: unknown) => {
const facade = notificationClickEventFacade(event);
if (facade) void click.handle(facade);
};
const onSubscriptionChange = (event: unknown) => {
const facade = functionalEventFacade(event);
if (!facade) return;
const taskControl = createLinkedAbortController(lifecycle.signal);
const processing = withAbortableDeadline(
async (signal) => {
let clients: readonly unknown[];
try {
clients = await dependencies.host.clients.matchAll({
type: "window",
includeUncontrolled: true,
});
} catch {
return nativeFailure("SUBSCRIPTION_RECONCILE", true);
}
if (signal.aborted) {
return webPushFailure("ABORTED", "SUBSCRIPTION_RECONCILE");
}
try {
for (const candidate of clients.slice(
0,
WEB_PUSH_LIMITS.clientHandoffCount,
)) {
if (signal.aborted) {
return webPushFailure(
"ABORTED",
"SUBSCRIPTION_RECONCILE",
);
}
const client = windowClientFacade(candidate);
client?.postMessage(
Object.freeze({
protocol: WEB_PUSH_PROTOCOLS.reconcileRequired,
}),
);
}
} catch {
return nativeFailure("SUBSCRIPTION_RECONCILE", true);
}
return webPushSuccess(undefined);
},
{
deadlineMs: WEB_PUSH_LIMITS.handlerDeadlineMs,
operation: "SUBSCRIPTION_RECONCILE",
signal: taskControl.signal,
scheduler: dependencies.scheduler,
},
).finally(taskControl.dispose);
const lifetime = processing.then((result) => {
observeWebPush(dependencies.observer, {
event: "web_push_subscription_rotated",
outcome: result.ok ? "SUCCEEDED" : "DEGRADED",
...(result.ok ? {} : { reason: result.error.code }),
});
});
try {
facade.waitUntil(lifetime);
} catch {
taskControl.abort();
void lifetime;
}
};
dependencies.host.addEventListener("push", onPush);
dependencies.host.addEventListener(
"notificationclick",
onNotificationClick,
);
dependencies.host.addEventListener(
"pushsubscriptionchange",
onSubscriptionChange,
);
let disposed = false;
return Object.freeze({
dispose() {
if (disposed) return;
disposed = true;
lifecycle.abort();
dependencies.host.removeEventListener("push", onPush);
dependencies.host.removeEventListener(
"notificationclick",
onNotificationClick,
);
dependencies.host.removeEventListener(
"pushsubscriptionchange",
onSubscriptionChange,
);
dependencies.fenceStore.close();
},
});
}
function functionalEventFacade(
value: unknown,
): FunctionalEventFacade | null {
try {
return isRecord(value) && typeof value.waitUntil === "function"
? (value as FunctionalEventFacade)
: null;
} catch {
return null;
}
}
function pushEventFacade(value: unknown): PushEventFacade | null {
if (
!isRecord(value) ||
typeof value.waitUntil !== "function" ||
!(
value.data === null ||
(isRecord(value.data) && typeof value.data.arrayBuffer === "function")
)
) {
return null;
}
return value as PushEventFacade;
}
function notificationClickEventFacade(
value: unknown,
): NotificationClickEventFacade | null {
if (
!isRecord(value) ||
typeof value.waitUntil !== "function" ||
!isRecord(value.notification) ||
typeof value.notification.close !== "function" ||
!Object.hasOwn(value.notification, "data")
) {
return null;
}
return value as NotificationClickEventFacade;
}
function windowClientFacade(value: unknown): WindowClientFacade | null {
if (
!isRecord(value) ||
typeof value.url !== "string" ||
typeof value.focus !== "function" ||
typeof value.postMessage !== "function"
) {
return null;
}
return value as WindowClientFacade;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}