openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
425 lines (423 loc) • 23.5 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { T as ensureColumn } from "./openclaw-state-db-cache-C7ljO0xP.js";
import { i as openOpenClawStateDatabase, s as runOpenClawStateWriteTransaction } from "./openclaw-state-db-BRTnL-D8.js";
import { o as sha256HexPrefixCore } from "./crypto-digest-C4hqTb_e.js";
import { a as updateConfigMachineState, r as readConfigMachineState } from "./config-machine-state-BCereLZr.js";
import { n as sanitizeExecApprovalDisplayText } from "./exec-approval-text-sanitize-C1BzZQH8.js";
//#region src/infra/push-web-preferences.ts
const WEB_PUSH_USER_PREFERENCES_KEY = "notifications.web.v1";
function normalizeWebPushDisplayLabel(value) {
if (typeof value !== "string") return;
return truncateUtf16Safe(sanitizeExecApprovalDisplayText(value.trim()), 80) || void 0;
}
const DEFAULT_WEB_PUSH_NOTIFICATION_PREFERENCES = {
categories: {
approvalRequested: true,
agentFinished: false,
agentQuestion: false,
humanMentioned: false,
scheduledTaskFailed: false,
backgroundTaskFailed: false
},
detailLevel: "private",
quietHours: {
enabled: false,
startMinute: 1320,
endMinute: 420,
timeZone: "UTC"
},
agentIds: []
};
const CATEGORY_KEYS = [
"approvalRequested",
"agentFinished",
"agentQuestion",
"humanMentioned",
"scheduledTaskFailed",
"backgroundTaskFailed"
];
const CATEGORY_TO_KEY = {
"approval-requested": "approvalRequested",
"agent-finished": "agentFinished",
"agent-question": "agentQuestion",
"human-mentioned": "humanMentioned",
"scheduled-task-failed": "scheduledTaskFailed",
"background-task-failed": "backgroundTaskFailed"
};
function detailLevel(value) {
return value === "private" || value === "identified" || value === "detailed" ? value : void 0;
}
function normalizeAgentIds(value) {
if (!Array.isArray(value)) return;
return [...new Set(value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry.length > 0 && entry.length <= 128))].slice(0, 128);
}
function normalizeQuietHours(value) {
if (!isRecord(value)) return;
const startMinute = value.startMinute;
const endMinute = value.endMinute;
const timeZone = typeof value.timeZone === "string" ? value.timeZone.trim() : "";
if (typeof value.enabled !== "boolean" || !Number.isInteger(startMinute) || !Number.isInteger(endMinute) || Number(startMinute) < 0 || Number(startMinute) > 1439 || Number(endMinute) < 0 || Number(endMinute) > 1439 || !timeZone || timeZone.length > 128) return;
try {
new Intl.DateTimeFormat("en-US", { timeZone }).format(0);
} catch {
return;
}
return {
enabled: value.enabled,
startMinute: Number(startMinute),
endMinute: Number(endMinute),
timeZone
};
}
function normalizeCategoryDefaults(value) {
const source = isRecord(value) ? value : {};
return Object.fromEntries(CATEGORY_KEYS.map((key) => [key, typeof source[key] === "boolean" ? source[key] : DEFAULT_WEB_PUSH_NOTIFICATION_PREFERENCES.categories[key]]));
}
function normalizeWebPushNotificationPreferences(value) {
const source = isRecord(value) ? value : {};
return {
categories: normalizeCategoryDefaults(source.categories),
detailLevel: detailLevel(source.detailLevel) ?? DEFAULT_WEB_PUSH_NOTIFICATION_PREFERENCES.detailLevel,
quietHours: normalizeQuietHours(source.quietHours) ?? DEFAULT_WEB_PUSH_NOTIFICATION_PREFERENCES.quietHours,
agentIds: normalizeAgentIds(source.agentIds) ?? []
};
}
function normalizeWebPushDevicePreferences(value) {
const source = isRecord(value) ? value : {};
const categorySource = isRecord(source.categories) ? source.categories : void 0;
const categories = categorySource ? Object.fromEntries(CATEGORY_KEYS.flatMap((key) => typeof categorySource[key] === "boolean" ? [[key, categorySource[key]]] : [])) : void 0;
const normalizedDetailLevel = detailLevel(source.detailLevel);
const normalizedQuietHours = normalizeQuietHours(source.quietHours);
const normalizedAgentIds = normalizeAgentIds(source.agentIds);
return {
enabled: typeof source.enabled === "boolean" ? source.enabled : true,
label: normalizeWebPushDisplayLabel(source.label) ?? "",
...categories && Object.keys(categories).length > 0 ? { categories } : {},
...normalizedDetailLevel ? { detailLevel: normalizedDetailLevel } : {},
...normalizedQuietHours ? { quietHours: normalizedQuietHours } : {},
...normalizedAgentIds ? { agentIds: normalizedAgentIds } : {}
};
}
function resolveEffectiveWebPushPreferences(params) {
const user = normalizeWebPushNotificationPreferences(params.user);
const device = normalizeWebPushDevicePreferences(params.device);
return {
enabled: device.enabled,
label: device.label,
categories: {
...user.categories,
...device.categories
},
detailLevel: device.detailLevel ?? user.detailLevel,
quietHours: device.quietHours ?? user.quietHours,
agentIds: device.agentIds ?? user.agentIds
};
}
function webPushCategoryEnabled(preferences, category) {
const key = CATEGORY_TO_KEY[category];
return key !== void 0 && preferences.enabled && preferences.categories[key] === true;
}
function isWebPushQuietHours(preferences, nowMs = Date.now()) {
const quiet = preferences.quietHours;
if (!quiet.enabled || quiet.startMinute === quiet.endMinute) return false;
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: quiet.timeZone,
hour: "2-digit",
minute: "2-digit",
hourCycle: "h23"
}).formatToParts(nowMs);
const hour = Number(parts.find((part) => part.type === "hour")?.value ?? 0);
const minute = Number(parts.find((part) => part.type === "minute")?.value ?? 0);
const current = hour * 60 + minute;
return quiet.startMinute < quiet.endMinute ? current >= quiet.startMinute && current < quiet.endMinute : current >= quiet.startMinute || current < quiet.endMinute;
}
function webPushAgentAllowed(preferences, agentId) {
return preferences.agentIds.length === 0 || Boolean(agentId && preferences.agentIds.includes(agentId));
}
//#endregion
//#region src/infra/push-web-store.ts
const WEB_PUSH_VAPID_STATE_KEY = "webPush.vapidKeys";
const DEFAULT_WEB_PUSH_VAPID_SUBJECT = "https://openclaw.ai";
const WEB_PUSH_MAX_ENDPOINT_LENGTH = 2048;
const WEB_PUSH_MAX_KEY_LENGTH = 512;
const WEB_PUSH_APPROVAL_RECOVERY_MAX_APPROVALS = 1024;
function createWebPushVapidKeyPair(publicKey, privateKey, subject) {
return {
publicKey,
privateKey,
subject
};
}
const ensuredWebPushBindingDatabases = /* @__PURE__ */ new WeakSet();
const ensuredWebPushApprovalDeliveryDatabases = /* @__PURE__ */ new WeakSet();
const WEB_PUSH_APPROVAL_DELIVERY_SCHEMA_SQL = `
CREATE TABLE IF NOT EXISTS web_push_approval_deliveries (
approval_id TEXT NOT NULL
REFERENCES operator_approvals(approval_id) ON DELETE CASCADE,
subscription_id TEXT NOT NULL
REFERENCES web_push_subscriptions(subscription_id) ON DELETE CASCADE,
device_id TEXT NOT NULL,
user_profile_id TEXT,
prepared_at_ms INTEGER NOT NULL,
PRIMARY KEY (approval_id, subscription_id)
) STRICT;
CREATE INDEX IF NOT EXISTS idx_web_push_approval_deliveries_subscription
ON web_push_approval_deliveries(subscription_id, approval_id);
`;
function webPushStateDatabaseOptions(stateDir) {
return stateDir ? { env: {
...process.env,
OPENCLAW_STATE_DIR: stateDir
} } : { env: process.env };
}
/** Adds downgrade-safe binding columns before the first Web Push store operation. */
function ensureWebPushSubscriptionBindingColumns(db) {
ensureColumn(db, "web_push_subscriptions", "device_id TEXT");
ensureColumn(db, "web_push_subscriptions", "user_profile_id TEXT");
ensureColumn(db, "web_push_subscriptions", "preferences_json TEXT");
}
function ensureWebPushSubscriptionBindingSchema(stateDir) {
const options = webPushStateDatabaseOptions(stateDir);
const database = openOpenClawStateDatabase(options);
if (ensuredWebPushBindingDatabases.has(database.db)) return;
runOpenClawStateWriteTransaction(({ db }) => ensureWebPushSubscriptionBindingColumns(db), options, { operationLabel: "web-push.subscription-binding.schema.ensure" });
ensuredWebPushBindingDatabases.add(database.db);
}
/** Lazily adds the restart-safe approval delivery table on first feature use. */
function ensureWebPushApprovalDeliveryTable(db) {
db.exec(WEB_PUSH_APPROVAL_DELIVERY_SCHEMA_SQL);
}
function ensureWebPushApprovalDeliverySchema(stateDir) {
const options = webPushStateDatabaseOptions(stateDir);
const database = openOpenClawStateDatabase(options);
if (ensuredWebPushApprovalDeliveryDatabases.has(database.db)) return;
runOpenClawStateWriteTransaction(({ db }) => ensureWebPushApprovalDeliveryTable(db), options, { operationLabel: "web-push.approval-delivery.schema.ensure" });
ensuredWebPushApprovalDeliveryDatabases.add(database.db);
}
function hashWebPushEndpoint(endpoint) {
return sha256HexPrefixCore(endpoint, 32);
}
function isValidWebPushEndpoint(endpoint) {
if (!endpoint || endpoint.length > WEB_PUSH_MAX_ENDPOINT_LENGTH) return false;
try {
return new URL(endpoint).protocol === "https:";
} catch {
return false;
}
}
function isValidWebPushKey(key) {
return typeof key === "string" && key.length > 0 && key.length <= WEB_PUSH_MAX_KEY_LENGTH;
}
function webPushSubscriptionFromRow(row) {
return {
subscriptionId: row.subscription_id,
endpoint: row.endpoint,
keys: {
p256dh: row.p256dh,
auth: row.auth
},
createdAtMs: row.created_at_ms,
updatedAtMs: row.updated_at_ms
};
}
function boundWebPushSubscriptionFromRow(row) {
if (!row.device_id) return null;
return {
...webPushSubscriptionFromRow(row),
deviceId: row.device_id,
userProfileId: row.user_profile_id,
devicePreferences: normalizeWebPushDevicePreferences(parseDevicePreferences(row.preferences_json))
};
}
function parseDevicePreferences(value) {
if (!value) return;
try {
return JSON.parse(value);
} catch {
return;
}
}
function webPushSubscriptionToRow(params) {
return {
endpoint_hash: params.endpointHash,
subscription_id: params.subscription.subscriptionId,
endpoint: params.subscription.endpoint,
p256dh: params.subscription.keys.p256dh,
auth: params.subscription.keys.auth,
device_id: params.binding?.deviceId ?? null,
user_profile_id: params.binding?.userProfileId ?? null,
preferences_json: null,
created_at_ms: params.subscription.createdAtMs,
updated_at_ms: params.subscription.updatedAtMs
};
}
function findBoundWebPushSubscriptionByEndpoint(params) {
ensureWebPushSubscriptionBindingSchema(params.stateDir);
const database = openOpenClawStateDatabase(webPushStateDatabaseOptions(params.stateDir));
const row = executeSqliteQueryTakeFirstSync(database.db, getNodeSqliteKysely(database.db).selectFrom("web_push_subscriptions").selectAll().where("endpoint_hash", "=", hashWebPushEndpoint(params.endpoint)).where("endpoint", "=", params.endpoint));
return row ? boundWebPushSubscriptionFromRow(row) : null;
}
function setWebPushSubscriptionPreferences(params) {
ensureWebPushSubscriptionBindingSchema(params.stateDir);
const options = webPushStateDatabaseOptions(params.stateDir);
return runOpenClawStateWriteTransaction(({ db }) => {
const result = executeSqliteQuerySync(db, getNodeSqliteKysely(db).updateTable("web_push_subscriptions").set({
preferences_json: JSON.stringify(normalizeWebPushDevicePreferences(params.preferences)),
updated_at_ms: Date.now()
}).where("endpoint_hash", "=", hashWebPushEndpoint(params.endpoint)).where("endpoint", "=", params.endpoint).where("device_id", "=", params.expectedDeviceId).where("user_profile_id", params.expectedUserProfileId === null ? "is" : "=", params.expectedUserProfileId));
return Number(result.numAffectedRows ?? 0) === 1;
}, options);
}
function webPushSubscriptionsEqual(left, right) {
return left.subscriptionId === right.subscriptionId && left.endpoint === right.endpoint && left.keys.p256dh === right.keys.p256dh && left.keys.auth === right.keys.auth && left.createdAtMs === right.createdAtMs && left.updatedAtMs === right.updatedAtMs;
}
function listWebPushSubscriptions(stateDir) {
ensureWebPushSubscriptionBindingSchema(stateDir);
const database = openOpenClawStateDatabase(webPushStateDatabaseOptions(stateDir));
const stateDb = getNodeSqliteKysely(database.db);
return executeSqliteQuerySync(database.db, stateDb.selectFrom("web_push_subscriptions").selectAll().orderBy("created_at_ms", "asc").orderBy("subscription_id", "asc")).rows.map(webPushSubscriptionFromRow);
}
/** Lists only subscriptions reconciled by an authenticated browser device. */
function listBoundWebPushSubscriptions(stateDir) {
ensureWebPushSubscriptionBindingSchema(stateDir);
const database = openOpenClawStateDatabase(webPushStateDatabaseOptions(stateDir));
return executeSqliteQuerySync(database.db, getNodeSqliteKysely(database.db).selectFrom("web_push_subscriptions").selectAll().where("device_id", "is not", null).orderBy("created_at_ms", "asc").orderBy("subscription_id", "asc")).rows.flatMap((row) => {
const subscription = boundWebPushSubscriptionFromRow(row);
return subscription ? [subscription] : [];
});
}
/**
* Record the subscriptions that may receive the request before network I/O.
* Definite failures are removed after send; retaining the crash-ambiguous set
* lets restart recovery replace any actionable notification that may exist.
*/
function prepareWebPushApprovalDeliveries(params) {
const subscriptionsById = new Map(params.subscriptions.map((subscription) => [subscription.subscriptionId, subscription]));
if (subscriptionsById.size === 0) return false;
ensureWebPushApprovalDeliverySchema(params.stateDir);
const options = webPushStateDatabaseOptions(params.stateDir);
return runOpenClawStateWriteTransaction(({ db }) => {
const stateDb = getNodeSqliteKysely(db);
if (executeSqliteQueryTakeFirstSync(db, stateDb.selectFrom("operator_approvals").select("status").where("approval_id", "=", params.approvalId))?.status !== "pending") return false;
executeSqliteQuerySync(db, stateDb.insertInto("web_push_approval_deliveries").values([...subscriptionsById.values()].map((subscription) => ({
approval_id: params.approvalId,
subscription_id: subscription.subscriptionId,
device_id: subscription.deviceId,
user_profile_id: subscription.userProfileId,
prepared_at_ms: params.preparedAtMs
}))).onConflict((conflict) => conflict.columns(["approval_id", "subscription_id"]).doUpdateSet({
device_id: (eb) => eb.ref("excluded.device_id"),
user_profile_id: (eb) => eb.ref("excluded.user_profile_id"),
prepared_at_ms: params.preparedAtMs
})));
return true;
}, options);
}
/** Load current targets and discard rows whose original browser ownership no longer matches. */
function listWebPushApprovalDeliveryTargets(params) {
ensureWebPushApprovalDeliverySchema(params.stateDir);
return runOpenClawStateWriteTransaction(({ db }) => {
const stateDb = getNodeSqliteKysely(db);
const rows = executeSqliteQuerySync(db, stateDb.selectFrom("web_push_approval_deliveries").innerJoin("web_push_subscriptions", "web_push_subscriptions.subscription_id", "web_push_approval_deliveries.subscription_id").selectAll("web_push_subscriptions").select(["web_push_approval_deliveries.device_id as delivery_device_id", "web_push_approval_deliveries.user_profile_id as delivery_user_profile_id"]).where("web_push_approval_deliveries.approval_id", "=", params.approvalId).orderBy("web_push_subscriptions.created_at_ms", "asc").orderBy("web_push_subscriptions.subscription_id", "asc")).rows;
const staleSubscriptionIds = new Set(rows.filter((row) => row.device_id !== row.delivery_device_id || row.user_profile_id !== row.delivery_user_profile_id).map((row) => row.subscription_id));
if (staleSubscriptionIds.size > 0) executeSqliteQuerySync(db, stateDb.deleteFrom("web_push_approval_deliveries").where("approval_id", "=", params.approvalId).where("subscription_id", "in", [...staleSubscriptionIds]));
return rows.flatMap((row) => {
if (staleSubscriptionIds.has(row.subscription_id)) return [];
const subscription = boundWebPushSubscriptionFromRow(row);
return subscription ? [subscription] : [];
});
}, webPushStateDatabaseOptions(params.stateDir));
}
/** Remove only targets whose terminal replacement was accepted. */
function deleteWebPushApprovalDeliveryTargets(params) {
const subscriptionIds = [...new Set(params.subscriptionIds)];
if (subscriptionIds.length === 0) return;
ensureWebPushApprovalDeliverySchema(params.stateDir);
runOpenClawStateWriteTransaction(({ db }) => {
executeSqliteQuerySync(db, getNodeSqliteKysely(db).deleteFrom("web_push_approval_deliveries").where("approval_id", "=", params.approvalId).where("subscription_id", "in", subscriptionIds));
}, webPushStateDatabaseOptions(params.stateDir));
}
/** Page through a stable snapshot of terminal approvals that still need replacement. */
function listTerminalWebPushApprovalDeliveryIds(params) {
ensureWebPushApprovalDeliverySchema(params.stateDir);
const database = openOpenClawStateDatabase(webPushStateDatabaseOptions(params.stateDir));
const stateDb = getNodeSqliteKysely(database.db);
const terminalApprovalQuery = () => stateDb.selectFrom("web_push_approval_deliveries").innerJoin("operator_approvals", "operator_approvals.approval_id", "web_push_approval_deliveries.approval_id").select("web_push_approval_deliveries.approval_id").distinct().where("operator_approvals.status", "!=", "pending");
const throughApprovalId = params.throughApprovalId ?? executeSqliteQueryTakeFirstSync(database.db, terminalApprovalQuery().orderBy("web_push_approval_deliveries.approval_id", "desc").limit(1))?.approval_id;
if (!throughApprovalId) return {
approvalIds: [],
nextAfterApprovalId: null,
throughApprovalId: null
};
let pageQuery = terminalApprovalQuery().where("web_push_approval_deliveries.approval_id", "<=", throughApprovalId);
if (params.afterApprovalId) pageQuery = pageQuery.where("web_push_approval_deliveries.approval_id", ">", params.afterApprovalId);
const rows = executeSqliteQuerySync(database.db, pageQuery.orderBy("web_push_approval_deliveries.approval_id", "asc").limit(1025)).rows;
const approvalIds = rows.slice(0, WEB_PUSH_APPROVAL_RECOVERY_MAX_APPROVALS).map((row) => row.approval_id);
return {
approvalIds,
nextAfterApprovalId: rows.length > WEB_PUSH_APPROVAL_RECOVERY_MAX_APPROVALS ? approvalIds.at(-1) ?? null : null,
throughApprovalId
};
}
var WebPushSubscriptionBindingError = class extends Error {};
/** Reread the endpoint row inside the write transaction before creating or updating it. */
function upsertWebPushSubscription(params) {
ensureWebPushSubscriptionBindingSchema(params.stateDir);
return runOpenClawStateWriteTransaction(({ db }) => {
const stateDb = getNodeSqliteKysely(db);
const existingRow = executeSqliteQueryTakeFirstSync(db, stateDb.selectFrom("web_push_subscriptions").selectAll().where("endpoint_hash", "=", params.endpointHash));
if (existingRow && existingRow.endpoint !== params.endpoint) throw new Error("web push endpoint hash collision");
const subscription = {
subscriptionId: existingRow?.subscription_id ?? params.candidateSubscriptionId,
endpoint: params.endpoint,
keys: { ...params.keys },
createdAtMs: existingRow?.created_at_ms ?? params.nowMs,
updatedAtMs: params.nowMs
};
const row = webPushSubscriptionToRow({
endpointHash: params.endpointHash,
subscription,
binding: params.binding
});
const bindingChanged = Boolean(existingRow && (existingRow.device_id !== row.device_id || existingRow.user_profile_id !== row.user_profile_id));
if (bindingChanged && existingRow && (existingRow.p256dh !== params.keys.p256dh || existingRow.auth !== params.keys.auth)) throw new WebPushSubscriptionBindingError("existing browser subscription keys required; reconnect from the owning browser");
executeSqliteQuerySync(db, stateDb.insertInto("web_push_subscriptions").values(row).onConflict((conflict) => conflict.column("endpoint_hash").doUpdateSet({
subscription_id: row.subscription_id,
endpoint: row.endpoint,
p256dh: row.p256dh,
auth: row.auth,
device_id: row.device_id,
user_profile_id: row.user_profile_id,
preferences_json: bindingChanged ? null : existingRow?.preferences_json ?? null,
updated_at_ms: row.updated_at_ms
})));
return subscription;
}, webPushStateDatabaseOptions(params.stateDir));
}
function deleteBoundWebPushSubscription(params) {
ensureWebPushSubscriptionBindingSchema(params.stateDir);
return runOpenClawStateWriteTransaction(({ db }) => {
const result = executeSqliteQuerySync(db, getNodeSqliteKysely(db).deleteFrom("web_push_subscriptions").where("endpoint_hash", "=", params.endpointHash).where("endpoint", "=", params.endpoint).where("device_id", "=", params.expectedDeviceId).where("user_profile_id", params.expectedUserProfileId === null ? "is" : "=", params.expectedUserProfileId));
return Number(result.numAffectedRows ?? 0) > 0;
}, webPushStateDatabaseOptions(params.stateDir));
}
/** Delete an expired send target only if no newer registration replaced it in flight. */
function deleteWebPushSubscriptionIfCurrent(params) {
const subscription = params.subscription;
ensureWebPushSubscriptionBindingSchema(params.stateDir);
return runOpenClawStateWriteTransaction(({ db }) => {
const result = executeSqliteQuerySync(db, getNodeSqliteKysely(db).deleteFrom("web_push_subscriptions").where("endpoint_hash", "=", params.endpointHash).where("subscription_id", "=", subscription.subscriptionId).where("endpoint", "=", subscription.endpoint).where("p256dh", "=", subscription.keys.p256dh).where("auth", "=", subscription.keys.auth).where("updated_at_ms", "=", subscription.updatedAtMs));
return Number(result.numAffectedRows ?? 0) > 0;
}, webPushStateDatabaseOptions(params.stateDir));
}
function readPersistedVapidKeyPair(stateDir) {
return readConfigMachineState("webPush.vapidKeys", webPushStateDatabaseOptions(stateDir)) ?? null;
}
/** First committed keypair wins so concurrent gateway bootstraps share one signing identity. */
function insertVapidKeyPairIfAbsent(params) {
return updateConfigMachineState(WEB_PUSH_VAPID_STATE_KEY, (current) => current ?? params.candidate, webPushStateDatabaseOptions(params.stateDir));
}
//#endregion
export { resolveEffectiveWebPushPreferences as A, webPushSubscriptionToRow as C, normalizeWebPushDevicePreferences as D, isWebPushQuietHours as E, webPushCategoryEnabled as M, normalizeWebPushDisplayLabel as O, webPushSubscriptionFromRow as S, WEB_PUSH_USER_PREFERENCES_KEY as T, listWebPushSubscriptions as _, deleteBoundWebPushSubscription as a, setWebPushSubscriptionPreferences as b, ensureWebPushSubscriptionBindingColumns as c, insertVapidKeyPairIfAbsent as d, isValidWebPushEndpoint as f, listWebPushApprovalDeliveryTargets as g, listTerminalWebPushApprovalDeliveryIds as h, createWebPushVapidKeyPair as i, webPushAgentAllowed as j, normalizeWebPushNotificationPreferences as k, findBoundWebPushSubscriptionByEndpoint as l, listBoundWebPushSubscriptions as m, WEB_PUSH_VAPID_STATE_KEY as n, deleteWebPushApprovalDeliveryTargets as o, isValidWebPushKey as p, WebPushSubscriptionBindingError as r, deleteWebPushSubscriptionIfCurrent as s, DEFAULT_WEB_PUSH_VAPID_SUBJECT as t, hashWebPushEndpoint as u, prepareWebPushApprovalDeliveries as v, webPushSubscriptionsEqual as w, upsertWebPushSubscription as x, readPersistedVapidKeyPair as y };