tempest-react-sdk 0.68.0 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +58 -30
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +53 -2
- package/dist/tempest-react-sdk.d.ts +53 -2
- package/dist/vision/core/canvas.cjs +1 -1
- package/dist/vision/core/canvas.cjs.map +1 -1
- package/dist/vision/core/canvas.js +37 -13
- package/dist/vision/core/canvas.js.map +1 -1
- package/dist/vision/core/dtypes.cjs +2 -0
- package/dist/vision/core/dtypes.cjs.map +1 -0
- package/dist/vision/core/dtypes.js +33 -0
- package/dist/vision/core/dtypes.js.map +1 -0
- package/dist/vision/core/graph.cjs +1 -1
- package/dist/vision/core/graph.cjs.map +1 -1
- package/dist/vision/core/graph.js +10 -2
- package/dist/vision/core/graph.js.map +1 -1
- package/dist/vision/core/metadata.cjs +2 -2
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js +103 -13
- package/dist/vision/core/metadata.js.map +1 -1
- package/dist/vision/core/providers.cjs +1 -1
- package/dist/vision/core/providers.cjs.map +1 -1
- package/dist/vision/core/providers.js +9 -6
- package/dist/vision/core/providers.js.map +1 -1
- package/dist/vision/core/session.cjs +1 -1
- package/dist/vision/core/session.cjs.map +1 -1
- package/dist/vision/core/session.js +115 -27
- package/dist/vision/core/session.js.map +1 -1
- package/dist/vision/fusion.cjs +1 -1
- package/dist/vision/fusion.cjs.map +1 -1
- package/dist/vision/fusion.js +28 -23
- package/dist/vision/fusion.js.map +1 -1
- package/dist/vision/index.cjs +1 -1
- package/dist/vision/index.cjs.map +1 -1
- package/dist/vision/index.js +20 -19
- package/dist/vision/index.js.map +1 -1
- package/dist/vision/io/image.cjs +1 -1
- package/dist/vision/io/image.cjs.map +1 -1
- package/dist/vision/io/image.js +55 -20
- package/dist/vision/io/image.js.map +1 -1
- package/dist/vision/postprocess/detection.cjs +1 -1
- package/dist/vision/postprocess/detection.cjs.map +1 -1
- package/dist/vision/postprocess/detection.js +42 -51
- package/dist/vision/postprocess/detection.js.map +1 -1
- package/dist/vision/postprocess/segmentation.cjs +1 -1
- package/dist/vision/postprocess/segmentation.cjs.map +1 -1
- package/dist/vision/postprocess/segmentation.js +45 -31
- package/dist/vision/postprocess/segmentation.js.map +1 -1
- package/dist/vision/preprocess/pipeline.cjs +1 -1
- package/dist/vision/preprocess/pipeline.cjs.map +1 -1
- package/dist/vision/preprocess/pipeline.js +24 -25
- package/dist/vision/preprocess/pipeline.js.map +1 -1
- package/dist/vision/tasks/base.cjs +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js +22 -7
- package/dist/vision/tasks/base.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js +64 -55
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision/tasks/detectClassify.cjs +1 -1
- package/dist/vision/tasks/detectClassify.cjs.map +1 -1
- package/dist/vision/tasks/detectClassify.js +80 -69
- package/dist/vision/tasks/detectClassify.js.map +1 -1
- package/dist/vision/tasks/detector.cjs +1 -1
- package/dist/vision/tasks/detector.cjs.map +1 -1
- package/dist/vision/tasks/detector.js +74 -68
- package/dist/vision/tasks/detector.js.map +1 -1
- package/dist/vision/tasks/segmenter.cjs +1 -1
- package/dist/vision/tasks/segmenter.cjs.map +1 -1
- package/dist/vision/tasks/segmenter.js +85 -62
- package/dist/vision/tasks/segmenter.js.map +1 -1
- package/dist/vision/types.cjs +1 -1
- package/dist/vision/types.cjs.map +1 -1
- package/dist/vision/types.js +24 -4
- package/dist/vision/types.js.map +1 -1
- package/dist/vision.cjs +1 -1
- package/dist/vision.d.ts +1153 -777
- package/dist/vision.js +22 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -320,7 +320,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
320
320
|
| `push` | `WebPushClient`, `WebPushUnsupportedError`, `WebPushPermissionDeniedError`, `usePushSubscription`, `urlBase64ToUint8Array`, `isPushSupported` |
|
|
321
321
|
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`), `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts` |
|
|
322
322
|
| `editor` _(subpath `tempest-react-sdk/editor`, opt. peers `@tiptap/react`+`@tiptap/starter-kit`)_ | `RichTextEditor` — themed tiptap v3 rich-text editor with toolbar (tiptap externalized; install it only if you use the editor) |
|
|
323
|
-
| `vision` _(subpath `tempest-react-sdk/vision`, opt. peer `onnxruntime-web`)_ | `Classifier`, `Detector`, `Segmenter`, `DetectClassify` (fused detect→classify pipeline) + `warmup()`, `raiseOnEmpty`/`NoDetectionsError`, `LetterboxPipeline`, `readFusionSpec` and results/labels/preprocess — on-device ONNX vision inference, vendored from `@mauriciobenjamin700/ort-vision-sdk-web` (onnxruntime-web externalized; install it only if you use vision). Plus browser capture primitives: `useCameraStream` (rear-camera `getUserMedia` + classified errors + `retry()`), `computeImageLuminance`/`isLuminanceAcceptable`/`LowLuminanceError`/`LUMINANCE_SAMPLE_MAX_EDGE` (BT.709 brightness), `useLiveLuminance` (live rAF sampling)
|
|
323
|
+
| `vision` _(subpath `tempest-react-sdk/vision`, opt. peer `onnxruntime-web`)_ | `Classifier`, `Detector`, `Segmenter`, `DetectClassify` (fused detect→classify pipeline, optionally with a segmentation stage) + `warmup()`, FP16 models, `cache` (Cache Storage), video/`VideoFrame` inputs, `raiseOnEmpty`/`NoDetectionsError`, `LetterboxPipeline`, `readFusionSpec` and results/labels/preprocess — on-device ONNX vision inference, vendored from `@mauriciobenjamin700/ort-vision-sdk-web` (onnxruntime-web externalized; install it only if you use vision). Plus browser capture primitives: `useCameraStream` (rear-camera `getUserMedia` + classified errors + `retry()`), `computeImageLuminance`/`isLuminanceAcceptable`/`LowLuminanceError`/`LUMINANCE_SAMPLE_MAX_EDGE` (BT.709 brightness), `useLiveLuminance` (live rAF sampling) |
|
|
324
324
|
| `tabular` _(subpath `tempest-react-sdk/tabular`, opt. peer `onnxruntime-web`)_ | `TabularPredictor`, `useTabularPredictor` — scikit-learn models exported by `tempest-fastapi-sdk`'s `export_sklearn_to_onnx`, running in the browser with no inference server. Offline by design: `fetchModelBytes`/`isModelCached`/`cacheModelBytes`/`clearModelCache` (Cache Storage, cache-first) plus `configureOrtAssets`/`ortAssetUrls`/`ORT_WASM_ASSETS` for the runtime's WebAssembly binaries, which ONNX Runtime Web fetches at session creation. Import the default `onnxruntime-web` entry — the WebGPU build has no `ai.onnx.ml` kernels |
|
|
325
325
|
| `imaging` _(subpath `tempest-react-sdk/imaging`, no deps)_ | `resizeImage`, `cropImage`, `rotateImage`, `flipImage`, `compressToTarget` (binary-searches quality into a byte budget), `createThumbnails` (several sizes from one decode), `captureFrame` (one frame out of a `<video>`, at the instant you ask for), `decodeImage`/`readImageInfo`, `encodeImage`, `supportsImageType`/`bestSupportedType`, plus `useImagePreview`/`useImageProcessing`. Canvas only — decodes with EXIF orientation applied (phone photos arrive upright), reports the format actually produced (asking for AVIF where there is no encoder silently returns PNG), and re-encoding drops EXIF, which is the privacy step as much as the bandwidth one. `captureFrame` is the one with teeth: `seeked` does not mean the new frame is readable, so the naive seek-then-draw returns the neighbouring frame with no error — and a `MediaRecorder` blob reports no duration until it is probed for one, which it does for you |
|
|
326
326
|
| `audio` | `createAudioPlayer`, `playAudio`, `stopAudio`, `useAudio`, `createAudioBus` / `useAudioBus` (gain above 100%, post-mix limiter, `setSinkId` routing), `DEFAULT_MAX_GAIN` |
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(){return globalThis}function t(
|
|
1
|
+
function e(){return globalThis}var t=[`body`,`image`,`tag`,`requireInteraction`,`vibrate`,`silent`,`renotify`,`timestamp`,`dir`,`lang`];function n(e,n,r){let i={icon:e.icon??n,badge:e.badge??r};for(let n of t)e[n]!==void 0&&(i[n]=e[n]);let a={url:e.url??`/`,...e.data??{}};if(e.actions){let t={};i.actions=e.actions.map(({url:e,...n})=>(e!==void 0&&(t[n.action]=e),n)),Object.keys(t).length>0&&(a.actionUrls=t)}return i.data=a,i}function r(t={}){let r=e(),{defaultTitle:i=`Notificação`,defaultIcon:a,defaultBadge:o,transform:s}=t;r.addEventListener(`push`,e=>{if(!e.data)return;let t;try{t=e.data.json()}catch{t={title:i,body:e.data.text()}}let c=s?s(t):t;if(!c)return;let l=c.title??i,u=n(c,a,o);e.waitUntil(r.registration.showNotification(l,u))})}function i(e,t){if(typeof e==`string`)return e;if(!e||typeof e!=`object`)return`/`;let n=e;if(t&&n.actionUrls&&typeof n.actionUrls==`object`){let e=n.actionUrls[t];if(typeof e==`string`)return e}return typeof n.url==`string`?n.url:`/`}function a(e,t){try{return new URL(t,e).href===new URL(e).href}catch{return!1}}function o(t={}){let n=e(),r=t.resolveUrl??i;n.addEventListener(`notificationclick`,e=>{e.notification.close();let t=r(e.notification.data,e.action);e.waitUntil((async()=>{let e=await n.clients.matchAll({type:`window`,includeUncontrolled:!0});for(let n of e)if(a(n.url,t))return n.focus();return n.clients.openWindow(t)})())})}function s(){let t=e();t.addEventListener(`message`,e=>{e.data?.type===`SKIP_WAITING`&&t.skipWaiting()})}exports.installNotificationClickHandler=o,exports.installPushHandler=r,exports.installSkipWaitingListener=s;
|
|
2
2
|
//# sourceMappingURL=create-push-handler.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-push-handler.cjs","names":[],"sources":["../../src/sw/create-push-handler.ts"],"sourcesContent":["/**\n * Service-worker context helpers for handling `push` and `notificationclick`\n * events. Import these inside your own `sw.ts` — they expect to run in the\n * service-worker global scope, not in the main thread.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installPushHandler, installNotificationClickHandler } from \"tempest-react-sdk\";\n *\n * installPushHandler({ defaultIcon: \"/icons/Logo.png\" });\n * installNotificationClickHandler();\n */\n\ninterface SwGlobal {\n registration: {\n showNotification(title: string, options?: NotificationOptions): Promise<void>;\n };\n clients: {\n matchAll(options: { type: \"window\"; includeUncontrolled?: boolean }): Promise<\n {\n url: string;\n focused: boolean;\n focus(): Promise<unknown>;\n navigate(url: string): Promise<unknown>;\n }[]\n >;\n openWindow(url: string): Promise<unknown>;\n };\n addEventListener(\n type: \"push\",\n listener: (event: {\n data: { json(): unknown; text(): string } | null;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n addEventListener(\n type: \"notificationclick\",\n listener: (event: {\n notification: { close(): void; data?: unknown };\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n skipWaiting(): Promise<void>;\n}\n\nfunction getSwScope(): SwGlobal {\n return globalThis as unknown as SwGlobal;\n}\n\nexport interface PushPayload {\n title?: string;\n body?: string;\n icon?: string;\n badge?: string;\n image?: string;\n tag?: string;\n url?: string;\n /** Arbitrary extra data forwarded to `event.notification.data`. */\n data?: Record<string, unknown>;\n}\n\nexport interface InstallPushHandlerOptions {\n /** Title used when the payload omits one. */\n defaultTitle?: string;\n /** Icon used when the payload omits one. */\n defaultIcon?: string;\n /** Badge image (mobile). */\n defaultBadge?: string;\n /**\n * Transform the raw payload before showing the notification. Return `null`\n * to suppress the notification entirely (e.g. silent pings).\n */\n transform?: (payload: PushPayload) => PushPayload | null;\n}\n\n/**\n * Install a `push` event listener that parses the payload as JSON (with a\n * plain-text fallback) and shows a notification.\n */\nexport function installPushHandler(options: InstallPushHandlerOptions = {}): void {\n const sw = getSwScope();\n const { defaultTitle = \"Notificação\", defaultIcon, defaultBadge, transform } = options;\n\n sw.addEventListener(\"push\", (event) => {\n if (!event.data) return;\n\n let raw: PushPayload;\n try {\n raw = event.data.json() as PushPayload;\n } catch {\n raw = { title: defaultTitle, body: event.data.text() };\n }\n\n const payload = transform ? transform(raw) : raw;\n if (!payload) return;\n\n const title = payload.title ?? defaultTitle;\n const notification: NotificationOptions & { image?: string } = {\n body: payload.body,\n icon: payload.icon ?? defaultIcon,\n badge: payload.badge ?? defaultBadge,\n image: payload.image,\n tag: payload.tag,\n data: { url: payload.url ?? \"/\", ...(payload.data ?? {}) },\n };\n\n event.waitUntil(sw.registration.showNotification(title, notification));\n });\n}\n\nexport interface InstallNotificationClickHandlerOptions {\n /** Resolve the destination URL from the notification data. Default: `data.url`. */\n resolveUrl?: (data: unknown) => string;\n}\n\n/**\n * Install a `notificationclick` handler that focuses an existing client when\n * possible and falls back to opening a new window.\n */\nexport function installNotificationClickHandler(\n options: InstallNotificationClickHandlerOptions = {},\n): void {\n const sw = getSwScope();\n const resolveUrl =\n options.resolveUrl ??\n ((data: unknown) => {\n if (typeof data === \"string\") return data;\n if (data && typeof data === \"object\" && \"url\" in data) {\n const url = (data as Record<string, unknown>).url;\n return typeof url === \"string\" ? url : \"/\";\n }\n return \"/\";\n });\n\n sw.addEventListener(\"notificationclick\", (event) => {\n event.notification.close();\n const target = resolveUrl(event.notification.data);\n\n event.waitUntil(\n (async () => {\n const clients = await sw.clients.matchAll({\n type: \"window\",\n includeUncontrolled: true,\n });\n for (const client of clients) {\n if (client.url.includes(target)) {\n return client.focus();\n }\n }\n return sw.clients.openWindow(target);\n })(),\n );\n });\n}\n\n/**\n * Install a `message` listener that activates a waiting worker when the host\n * app sends `{ type: \"SKIP_WAITING\" }`.\n */\nexport function installSkipWaitingListener(): void {\n const sw = getSwScope() as SwGlobal & {\n addEventListener(\n type: \"message\",\n listener: (event: { data?: { type?: string } }) => void,\n ): void;\n };\n sw.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"SKIP_WAITING\") {\n void sw.skipWaiting();\n }\n });\n}\n"],"mappings":"AA6CA,SAAS,GAAuB,CAC5B,OAAO,UACX,CAgCA,SAAgB,EAAmB,EAAqC,CAAC,EAAS,CAC9E,IAAM,EAAK,EAAW,EAChB,CAAE,eAAe,cAAe,cAAa,eAAc,aAAc,EAE/E,EAAG,iBAAiB,OAAS,GAAU,CACnC,GAAI,CAAC,EAAM,KAAM,OAEjB,IAAI,EACJ,GAAI,CACA,EAAM,EAAM,KAAK,KAAK,CAC1B,MAAQ,CACJ,EAAM,CAAE,MAAO,EAAc,KAAM,EAAM,KAAK,KAAK,CAAE,CACzD,CAEA,IAAM,EAAU,EAAY,EAAU,CAAG,EAAI,EAC7C,GAAI,CAAC,EAAS,OAEd,IAAM,EAAQ,EAAQ,OAAS,EACzB,EAAyD,CAC3D,KAAM,EAAQ,KACd,KAAM,EAAQ,MAAQ,EACtB,MAAO,EAAQ,OAAS,EACxB,MAAO,EAAQ,MACf,IAAK,EAAQ,IACb,KAAM,CAAE,IAAK,EAAQ,KAAO,IAAK,GAAI,EAAQ,MAAQ,CAAC,CAAG,CAC7D,EAEA,EAAM,UAAU,EAAG,aAAa,iBAAiB,EAAO,CAAY,CAAC,CACzE,CAAC,CACL,CAWA,SAAgB,EACZ,EAAkD,CAAC,EAC/C,CACJ,IAAM,EAAK,EAAW,EAChB,EACF,EAAQ,aACN,GAAkB,CAChB,GAAI,OAAO,GAAS,SAAU,OAAO,EACrC,GAAI,GAAQ,OAAO,GAAS,UAAY,QAAS,EAAM,CACnD,IAAM,EAAO,EAAiC,IAC9C,OAAO,OAAO,GAAQ,SAAW,EAAM,GAC3C,CACA,MAAO,GACX,GAEJ,EAAG,iBAAiB,oBAAsB,GAAU,CAChD,EAAM,aAAa,MAAM,EACzB,IAAM,EAAS,EAAW,EAAM,aAAa,IAAI,EAEjD,EAAM,WACD,SAAY,CACT,IAAM,EAAU,MAAM,EAAG,QAAQ,SAAS,CACtC,KAAM,SACN,oBAAqB,EACzB,CAAC,EACD,IAAK,IAAM,KAAU,EACjB,GAAI,EAAO,IAAI,SAAS,CAAM,EAC1B,OAAO,EAAO,MAAM,EAG5B,OAAO,EAAG,QAAQ,WAAW,CAAM,CACvC,EAAA,CAAG,CACP,CACJ,CAAC,CACL,CAMA,SAAgB,GAAmC,CAC/C,IAAM,EAAK,EAAW,EAMtB,EAAG,iBAAiB,UAAY,GAAU,CAClC,EAAM,MAAM,OAAS,gBACrB,EAAQ,YAAY,CAE5B,CAAC,CACL"}
|
|
1
|
+
{"version":3,"file":"create-push-handler.cjs","names":[],"sources":["../../src/sw/create-push-handler.ts"],"sourcesContent":["/**\n * Service-worker context helpers for handling `push` and `notificationclick`\n * events. Import these inside your own `sw.ts` — they expect to run in the\n * service-worker global scope, not in the main thread.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installPushHandler, installNotificationClickHandler } from \"tempest-react-sdk\";\n *\n * installPushHandler({ defaultIcon: \"/icons/Logo.png\" });\n * installNotificationClickHandler();\n */\n\ninterface SwGlobal {\n registration: {\n showNotification(title: string, options?: NotificationOptions): Promise<void>;\n };\n clients: {\n matchAll(options: { type: \"window\"; includeUncontrolled?: boolean }): Promise<\n {\n url: string;\n focused: boolean;\n focus(): Promise<unknown>;\n navigate(url: string): Promise<unknown>;\n }[]\n >;\n openWindow(url: string): Promise<unknown>;\n };\n addEventListener(\n type: \"push\",\n listener: (event: {\n data: { json(): unknown; text(): string } | null;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n addEventListener(\n type: \"notificationclick\",\n listener: (event: {\n notification: { close(): void; data?: unknown };\n action?: string;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n skipWaiting(): Promise<void>;\n}\n\nfunction getSwScope(): SwGlobal {\n return globalThis as unknown as SwGlobal;\n}\n\n/**\n * A notification button as the push payload describes it.\n *\n * `action`, `title` and `icon` are forwarded to the browser as a\n * `NotificationAction`. `url` is not part of that type — the browser would drop\n * it — so {@link installPushHandler} moves it into `data.actionUrls`, where\n * {@link installNotificationClickHandler} reads it back when that button is\n * clicked.\n */\nexport interface PushNotificationAction {\n /** Identifier delivered as `event.action` when the button is clicked. */\n action: string;\n /** Button label. */\n title: string;\n /** Button icon URL. */\n icon?: string;\n /** URL opened when this button is clicked. Falls back to the top-level `url`. */\n url?: string;\n}\n\nexport interface PushPayload {\n title?: string;\n body?: string;\n icon?: string;\n badge?: string;\n image?: string;\n tag?: string;\n /** URL opened when the notification body is clicked. Default: `\"/\"`. */\n url?: string;\n /** Buttons rendered on the notification (browsers show up to `Notification.maxActions`). */\n actions?: PushNotificationAction[];\n /** Keep the notification on screen until the user interacts with it. */\n requireInteraction?: boolean;\n /** Vibration pattern in milliseconds (mobile). */\n vibrate?: number | number[];\n /** Show the notification without sound or vibration. */\n silent?: boolean;\n /** Alert again when a notification with the same `tag` replaces an older one. */\n renotify?: boolean;\n /** Event time in epoch milliseconds, shown by some platforms. */\n timestamp?: number;\n /** Text direction of the title and body. */\n dir?: NotificationDirection;\n /** BCP 47 language tag of the title and body. */\n lang?: string;\n /** Arbitrary extra data forwarded to `event.notification.data`. */\n data?: Record<string, unknown>;\n}\n\n/**\n * `NotificationOptions` plus the members TypeScript's DOM lib leaves out\n * because not every engine implements them. Chromium honours all of them;\n * engines without support ignore the unknown keys.\n */\ntype ExtendedNotificationOptions = NotificationOptions & {\n image?: string;\n actions?: Omit<PushNotificationAction, \"url\">[];\n vibrate?: number | number[];\n renotify?: boolean;\n timestamp?: number;\n};\n\n/**\n * Payload keys copied verbatim onto the notification options when present.\n */\nconst FORWARDED_OPTIONS = [\n \"body\",\n \"image\",\n \"tag\",\n \"requireInteraction\",\n \"vibrate\",\n \"silent\",\n \"renotify\",\n \"timestamp\",\n \"dir\",\n \"lang\",\n] as const satisfies readonly (keyof PushPayload & keyof ExtendedNotificationOptions)[];\n\n/**\n * Build the `showNotification` options from a push payload.\n *\n * Keys the payload omits stay absent instead of being set to `undefined`, so\n * the browser applies its own defaults. Per-action `url`s are collected into\n * `data.actionUrls` (only when at least one action carries one) and stripped\n * from the actions handed to the browser.\n *\n * @param payload - The parsed (and transformed) push payload.\n * @param defaultIcon - Icon used when the payload omits one.\n * @param defaultBadge - Badge used when the payload omits one.\n * @returns The options object for `registration.showNotification`.\n */\nfunction buildNotificationOptions(\n payload: PushPayload,\n defaultIcon: string | undefined,\n defaultBadge: string | undefined,\n): ExtendedNotificationOptions {\n const options: Record<string, unknown> = {\n icon: payload.icon ?? defaultIcon,\n badge: payload.badge ?? defaultBadge,\n };\n for (const key of FORWARDED_OPTIONS) {\n if (payload[key] !== undefined) options[key] = payload[key];\n }\n\n const data: Record<string, unknown> = { url: payload.url ?? \"/\", ...(payload.data ?? {}) };\n if (payload.actions) {\n const actionUrls: Record<string, string> = {};\n options.actions = payload.actions.map(({ url, ...action }) => {\n if (url !== undefined) actionUrls[action.action] = url;\n return action;\n });\n if (Object.keys(actionUrls).length > 0) data.actionUrls = actionUrls;\n }\n options.data = data;\n return options as ExtendedNotificationOptions;\n}\n\nexport interface InstallPushHandlerOptions {\n /** Title used when the payload omits one. */\n defaultTitle?: string;\n /** Icon used when the payload omits one. */\n defaultIcon?: string;\n /** Badge image (mobile). */\n defaultBadge?: string;\n /**\n * Transform the raw payload before showing the notification. Return `null`\n * to suppress the notification entirely (e.g. silent pings).\n */\n transform?: (payload: PushPayload) => PushPayload | null;\n}\n\n/**\n * Install a `push` event listener that parses the payload as JSON (with a\n * plain-text fallback) and shows a notification.\n *\n * Every `NotificationOptions` member the payload carries is forwarded —\n * `actions`, `requireInteraction`, `vibrate`, `silent`, `renotify`,\n * `timestamp`, `dir` and `lang` alongside `body`, `icon`, `badge`, `image` and\n * `tag`. `data` receives the top-level `url` (default `\"/\"`), the payload's\n * own `data`, and `actionUrls` mapping each action id to its `url`.\n */\nexport function installPushHandler(options: InstallPushHandlerOptions = {}): void {\n const sw = getSwScope();\n const { defaultTitle = \"Notificação\", defaultIcon, defaultBadge, transform } = options;\n\n sw.addEventListener(\"push\", (event) => {\n if (!event.data) return;\n\n let raw: PushPayload;\n try {\n raw = event.data.json() as PushPayload;\n } catch {\n raw = { title: defaultTitle, body: event.data.text() };\n }\n\n const payload = transform ? transform(raw) : raw;\n if (!payload) return;\n\n const title = payload.title ?? defaultTitle;\n const notification = buildNotificationOptions(payload, defaultIcon, defaultBadge);\n\n event.waitUntil(sw.registration.showNotification(title, notification));\n });\n}\n\nexport interface InstallNotificationClickHandlerOptions {\n /**\n * Resolve the destination URL from the notification data and the clicked\n * action id (`\"\"` or `undefined` for a click on the body). Default: the\n * action's entry in `data.actionUrls`, else `data.url`, else `\"/\"`.\n */\n resolveUrl?: (data: unknown, action: string | undefined) => string;\n}\n\n/**\n * Default URL resolution for a notification click.\n *\n * @param data - `event.notification.data`, as {@link installPushHandler} wrote it.\n * @param action - `event.action`; empty or `undefined` for a click on the body.\n * @returns The action's URL when it has one, else `data.url`, else `\"/\"`.\n */\nfunction defaultResolveUrl(data: unknown, action: string | undefined): string {\n if (typeof data === \"string\") return data;\n if (!data || typeof data !== \"object\") return \"/\";\n const record = data as Record<string, unknown>;\n if (action && record.actionUrls && typeof record.actionUrls === \"object\") {\n const actionUrl = (record.actionUrls as Record<string, unknown>)[action];\n if (typeof actionUrl === \"string\") return actionUrl;\n }\n return typeof record.url === \"string\" ? record.url : \"/\";\n}\n\n/**\n * Whether an open window client is already showing `target`.\n *\n * `target` is resolved against the client's own URL, so a relative path\n * compares on the client's origin, and the comparison is on the full href.\n * A substring test would let `/events/1` match a client on `/events/10`, and\n * `/` match every client.\n *\n * @param clientUrl - The window client's absolute URL.\n * @param target - The URL the click resolved to, absolute or relative.\n * @returns `true` when both point at the same document.\n */\nfunction clientShowsTarget(clientUrl: string, target: string): boolean {\n try {\n return new URL(target, clientUrl).href === new URL(clientUrl).href;\n } catch {\n return false;\n }\n}\n\n/**\n * Install a `notificationclick` handler that focuses an existing client when\n * possible and falls back to opening a new window.\n *\n * A click on an action button opens that action's URL (see\n * {@link PushNotificationAction.url}); a click on the body, or on an action\n * without one, opens the top-level `url`.\n */\nexport function installNotificationClickHandler(\n options: InstallNotificationClickHandlerOptions = {},\n): void {\n const sw = getSwScope();\n const resolveUrl = options.resolveUrl ?? defaultResolveUrl;\n\n sw.addEventListener(\"notificationclick\", (event) => {\n event.notification.close();\n const target = resolveUrl(event.notification.data, event.action);\n\n event.waitUntil(\n (async () => {\n const clients = await sw.clients.matchAll({\n type: \"window\",\n includeUncontrolled: true,\n });\n for (const client of clients) {\n if (clientShowsTarget(client.url, target)) {\n return client.focus();\n }\n }\n return sw.clients.openWindow(target);\n })(),\n );\n });\n}\n\n/**\n * Install a `message` listener that activates a waiting worker when the host\n * app sends `{ type: \"SKIP_WAITING\" }`.\n */\nexport function installSkipWaitingListener(): void {\n const sw = getSwScope() as SwGlobal & {\n addEventListener(\n type: \"message\",\n listener: (event: { data?: { type?: string } }) => void,\n ): void;\n };\n sw.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"SKIP_WAITING\") {\n void sw.skipWaiting();\n }\n });\n}\n"],"mappings":"AA8CA,SAAS,GAAuB,CAC5B,OAAO,UACX,CAmEA,IAAM,EAAoB,CACtB,OACA,QACA,MACA,qBACA,UACA,SACA,WACA,YACA,MACA,MACJ,EAeA,SAAS,EACL,EACA,EACA,EAC2B,CAC3B,IAAM,EAAmC,CACrC,KAAM,EAAQ,MAAQ,EACtB,MAAO,EAAQ,OAAS,CAC5B,EACA,IAAK,IAAM,KAAO,EACV,EAAQ,KAAS,IAAA,KAAW,EAAQ,GAAO,EAAQ,IAG3D,IAAM,EAAgC,CAAE,IAAK,EAAQ,KAAO,IAAK,GAAI,EAAQ,MAAQ,CAAC,CAAG,EACzF,GAAI,EAAQ,QAAS,CACjB,IAAM,EAAqC,CAAC,EAC5C,EAAQ,QAAU,EAAQ,QAAQ,KAAK,CAAE,MAAK,GAAG,MACzC,IAAQ,IAAA,KAAW,EAAW,EAAO,QAAU,GAC5C,EACV,EACG,OAAO,KAAK,CAAU,CAAC,CAAC,OAAS,IAAG,EAAK,WAAa,EAC9D,CAEA,MADA,GAAQ,KAAO,EACR,CACX,CA0BA,SAAgB,EAAmB,EAAqC,CAAC,EAAS,CAC9E,IAAM,EAAK,EAAW,EAChB,CAAE,eAAe,cAAe,cAAa,eAAc,aAAc,EAE/E,EAAG,iBAAiB,OAAS,GAAU,CACnC,GAAI,CAAC,EAAM,KAAM,OAEjB,IAAI,EACJ,GAAI,CACA,EAAM,EAAM,KAAK,KAAK,CAC1B,MAAQ,CACJ,EAAM,CAAE,MAAO,EAAc,KAAM,EAAM,KAAK,KAAK,CAAE,CACzD,CAEA,IAAM,EAAU,EAAY,EAAU,CAAG,EAAI,EAC7C,GAAI,CAAC,EAAS,OAEd,IAAM,EAAQ,EAAQ,OAAS,EACzB,EAAe,EAAyB,EAAS,EAAa,CAAY,EAEhF,EAAM,UAAU,EAAG,aAAa,iBAAiB,EAAO,CAAY,CAAC,CACzE,CAAC,CACL,CAkBA,SAAS,EAAkB,EAAe,EAAoC,CAC1E,GAAI,OAAO,GAAS,SAAU,OAAO,EACrC,GAAI,CAAC,GAAQ,OAAO,GAAS,SAAU,MAAO,IAC9C,IAAM,EAAS,EACf,GAAI,GAAU,EAAO,YAAc,OAAO,EAAO,YAAe,SAAU,CACtE,IAAM,EAAa,EAAO,WAAuC,GACjE,GAAI,OAAO,GAAc,SAAU,OAAO,CAC9C,CACA,OAAO,OAAO,EAAO,KAAQ,SAAW,EAAO,IAAM,GACzD,CAcA,SAAS,EAAkB,EAAmB,EAAyB,CACnE,GAAI,CACA,OAAO,IAAI,IAAI,EAAQ,CAAS,CAAC,CAAC,OAAS,IAAI,IAAI,CAAS,CAAC,CAAC,IAClE,MAAQ,CACJ,MAAO,EACX,CACJ,CAUA,SAAgB,EACZ,EAAkD,CAAC,EAC/C,CACJ,IAAM,EAAK,EAAW,EAChB,EAAa,EAAQ,YAAc,EAEzC,EAAG,iBAAiB,oBAAsB,GAAU,CAChD,EAAM,aAAa,MAAM,EACzB,IAAM,EAAS,EAAW,EAAM,aAAa,KAAM,EAAM,MAAM,EAE/D,EAAM,WACD,SAAY,CACT,IAAM,EAAU,MAAM,EAAG,QAAQ,SAAS,CACtC,KAAM,SACN,oBAAqB,EACzB,CAAC,EACD,IAAK,IAAM,KAAU,EACjB,GAAI,EAAkB,EAAO,IAAK,CAAM,EACpC,OAAO,EAAO,MAAM,EAG5B,OAAO,EAAG,QAAQ,WAAW,CAAM,CACvC,EAAA,CAAG,CACP,CACJ,CAAC,CACL,CAMA,SAAgB,GAAmC,CAC/C,IAAM,EAAK,EAAW,EAMtB,EAAG,iBAAiB,UAAY,GAAU,CAClC,EAAM,MAAM,OAAS,gBACrB,EAAQ,YAAY,CAE5B,CAAC,CACL"}
|
|
@@ -2,64 +2,92 @@
|
|
|
2
2
|
function e() {
|
|
3
3
|
return globalThis;
|
|
4
4
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
var t = [
|
|
6
|
+
"body",
|
|
7
|
+
"image",
|
|
8
|
+
"tag",
|
|
9
|
+
"requireInteraction",
|
|
10
|
+
"vibrate",
|
|
11
|
+
"silent",
|
|
12
|
+
"renotify",
|
|
13
|
+
"timestamp",
|
|
14
|
+
"dir",
|
|
15
|
+
"lang"
|
|
16
|
+
];
|
|
17
|
+
function n(e, n, r) {
|
|
18
|
+
let i = {
|
|
19
|
+
icon: e.icon ?? n,
|
|
20
|
+
badge: e.badge ?? r
|
|
21
|
+
};
|
|
22
|
+
for (let n of t) e[n] !== void 0 && (i[n] = e[n]);
|
|
23
|
+
let a = {
|
|
24
|
+
url: e.url ?? "/",
|
|
25
|
+
...e.data ?? {}
|
|
26
|
+
};
|
|
27
|
+
if (e.actions) {
|
|
28
|
+
let t = {};
|
|
29
|
+
i.actions = e.actions.map(({ url: e, ...n }) => (e !== void 0 && (t[n.action] = e), n)), Object.keys(t).length > 0 && (a.actionUrls = t);
|
|
30
|
+
}
|
|
31
|
+
return i.data = a, i;
|
|
32
|
+
}
|
|
33
|
+
function r(t = {}) {
|
|
34
|
+
let r = e(), { defaultTitle: i = "Notificação", defaultIcon: a, defaultBadge: o, transform: s } = t;
|
|
35
|
+
r.addEventListener("push", (e) => {
|
|
8
36
|
if (!e.data) return;
|
|
9
37
|
let t;
|
|
10
38
|
try {
|
|
11
39
|
t = e.data.json();
|
|
12
40
|
} catch {
|
|
13
41
|
t = {
|
|
14
|
-
title:
|
|
42
|
+
title: i,
|
|
15
43
|
body: e.data.text()
|
|
16
44
|
};
|
|
17
45
|
}
|
|
18
|
-
let
|
|
19
|
-
if (!
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
icon: s.icon ?? i,
|
|
23
|
-
badge: s.badge ?? a,
|
|
24
|
-
image: s.image,
|
|
25
|
-
tag: s.tag,
|
|
26
|
-
data: {
|
|
27
|
-
url: s.url ?? "/",
|
|
28
|
-
...s.data ?? {}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
e.waitUntil(n.registration.showNotification(c, l));
|
|
46
|
+
let c = s ? s(t) : t;
|
|
47
|
+
if (!c) return;
|
|
48
|
+
let l = c.title ?? i, u = n(c, a, o);
|
|
49
|
+
e.waitUntil(r.registration.showNotification(l, u));
|
|
32
50
|
});
|
|
33
51
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
52
|
+
function i(e, t) {
|
|
53
|
+
if (typeof e == "string") return e;
|
|
54
|
+
if (!e || typeof e != "object") return "/";
|
|
55
|
+
let n = e;
|
|
56
|
+
if (t && n.actionUrls && typeof n.actionUrls == "object") {
|
|
57
|
+
let e = n.actionUrls[t];
|
|
36
58
|
if (typeof e == "string") return e;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
}
|
|
60
|
+
return typeof n.url == "string" ? n.url : "/";
|
|
61
|
+
}
|
|
62
|
+
function a(e, t) {
|
|
63
|
+
try {
|
|
64
|
+
return new URL(t, e).href === new URL(e).href;
|
|
65
|
+
} catch {
|
|
66
|
+
return !1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function o(t = {}) {
|
|
70
|
+
let n = e(), r = t.resolveUrl ?? i;
|
|
43
71
|
n.addEventListener("notificationclick", (e) => {
|
|
44
72
|
e.notification.close();
|
|
45
|
-
let t = r(e.notification.data);
|
|
73
|
+
let t = r(e.notification.data, e.action);
|
|
46
74
|
e.waitUntil((async () => {
|
|
47
75
|
let e = await n.clients.matchAll({
|
|
48
76
|
type: "window",
|
|
49
77
|
includeUncontrolled: !0
|
|
50
78
|
});
|
|
51
|
-
for (let n of e) if (n.url
|
|
79
|
+
for (let n of e) if (a(n.url, t)) return n.focus();
|
|
52
80
|
return n.clients.openWindow(t);
|
|
53
81
|
})());
|
|
54
82
|
});
|
|
55
83
|
}
|
|
56
|
-
function
|
|
84
|
+
function s() {
|
|
57
85
|
let t = e();
|
|
58
86
|
t.addEventListener("message", (e) => {
|
|
59
87
|
e.data?.type === "SKIP_WAITING" && t.skipWaiting();
|
|
60
88
|
});
|
|
61
89
|
}
|
|
62
90
|
//#endregion
|
|
63
|
-
export {
|
|
91
|
+
export { o as installNotificationClickHandler, r as installPushHandler, s as installSkipWaitingListener };
|
|
64
92
|
|
|
65
93
|
//# sourceMappingURL=create-push-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-push-handler.js","names":[],"sources":["../../src/sw/create-push-handler.ts"],"sourcesContent":["/**\n * Service-worker context helpers for handling `push` and `notificationclick`\n * events. Import these inside your own `sw.ts` — they expect to run in the\n * service-worker global scope, not in the main thread.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installPushHandler, installNotificationClickHandler } from \"tempest-react-sdk\";\n *\n * installPushHandler({ defaultIcon: \"/icons/Logo.png\" });\n * installNotificationClickHandler();\n */\n\ninterface SwGlobal {\n registration: {\n showNotification(title: string, options?: NotificationOptions): Promise<void>;\n };\n clients: {\n matchAll(options: { type: \"window\"; includeUncontrolled?: boolean }): Promise<\n {\n url: string;\n focused: boolean;\n focus(): Promise<unknown>;\n navigate(url: string): Promise<unknown>;\n }[]\n >;\n openWindow(url: string): Promise<unknown>;\n };\n addEventListener(\n type: \"push\",\n listener: (event: {\n data: { json(): unknown; text(): string } | null;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n addEventListener(\n type: \"notificationclick\",\n listener: (event: {\n notification: { close(): void; data?: unknown };\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n skipWaiting(): Promise<void>;\n}\n\nfunction getSwScope(): SwGlobal {\n return globalThis as unknown as SwGlobal;\n}\n\nexport interface PushPayload {\n title?: string;\n body?: string;\n icon?: string;\n badge?: string;\n image?: string;\n tag?: string;\n url?: string;\n /** Arbitrary extra data forwarded to `event.notification.data`. */\n data?: Record<string, unknown>;\n}\n\nexport interface InstallPushHandlerOptions {\n /** Title used when the payload omits one. */\n defaultTitle?: string;\n /** Icon used when the payload omits one. */\n defaultIcon?: string;\n /** Badge image (mobile). */\n defaultBadge?: string;\n /**\n * Transform the raw payload before showing the notification. Return `null`\n * to suppress the notification entirely (e.g. silent pings).\n */\n transform?: (payload: PushPayload) => PushPayload | null;\n}\n\n/**\n * Install a `push` event listener that parses the payload as JSON (with a\n * plain-text fallback) and shows a notification.\n */\nexport function installPushHandler(options: InstallPushHandlerOptions = {}): void {\n const sw = getSwScope();\n const { defaultTitle = \"Notificação\", defaultIcon, defaultBadge, transform } = options;\n\n sw.addEventListener(\"push\", (event) => {\n if (!event.data) return;\n\n let raw: PushPayload;\n try {\n raw = event.data.json() as PushPayload;\n } catch {\n raw = { title: defaultTitle, body: event.data.text() };\n }\n\n const payload = transform ? transform(raw) : raw;\n if (!payload) return;\n\n const title = payload.title ?? defaultTitle;\n const notification: NotificationOptions & { image?: string } = {\n body: payload.body,\n icon: payload.icon ?? defaultIcon,\n badge: payload.badge ?? defaultBadge,\n image: payload.image,\n tag: payload.tag,\n data: { url: payload.url ?? \"/\", ...(payload.data ?? {}) },\n };\n\n event.waitUntil(sw.registration.showNotification(title, notification));\n });\n}\n\nexport interface InstallNotificationClickHandlerOptions {\n /** Resolve the destination URL from the notification data. Default: `data.url`. */\n resolveUrl?: (data: unknown) => string;\n}\n\n/**\n * Install a `notificationclick` handler that focuses an existing client when\n * possible and falls back to opening a new window.\n */\nexport function installNotificationClickHandler(\n options: InstallNotificationClickHandlerOptions = {},\n): void {\n const sw = getSwScope();\n const resolveUrl =\n options.resolveUrl ??\n ((data: unknown) => {\n if (typeof data === \"string\") return data;\n if (data && typeof data === \"object\" && \"url\" in data) {\n const url = (data as Record<string, unknown>).url;\n return typeof url === \"string\" ? url : \"/\";\n }\n return \"/\";\n });\n\n sw.addEventListener(\"notificationclick\", (event) => {\n event.notification.close();\n const target = resolveUrl(event.notification.data);\n\n event.waitUntil(\n (async () => {\n const clients = await sw.clients.matchAll({\n type: \"window\",\n includeUncontrolled: true,\n });\n for (const client of clients) {\n if (client.url.includes(target)) {\n return client.focus();\n }\n }\n return sw.clients.openWindow(target);\n })(),\n );\n });\n}\n\n/**\n * Install a `message` listener that activates a waiting worker when the host\n * app sends `{ type: \"SKIP_WAITING\" }`.\n */\nexport function installSkipWaitingListener(): void {\n const sw = getSwScope() as SwGlobal & {\n addEventListener(\n type: \"message\",\n listener: (event: { data?: { type?: string } }) => void,\n ): void;\n };\n sw.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"SKIP_WAITING\") {\n void sw.skipWaiting();\n }\n });\n}\n"],"mappings":";AA6CA,SAAS,IAAuB;CAC5B,OAAO;AACX;AAgCA,SAAgB,EAAmB,IAAqC,CAAC,GAAS;CAC9E,IAAM,IAAK,EAAW,GAChB,EAAE,kBAAe,eAAe,gBAAa,iBAAc,iBAAc;CAE/E,EAAG,iBAAiB,SAAS,MAAU;EACnC,IAAI,CAAC,EAAM,MAAM;EAEjB,IAAI;EACJ,IAAI;GACA,IAAM,EAAM,KAAK,KAAK;EAC1B,QAAQ;GACJ,IAAM;IAAE,OAAO;IAAc,MAAM,EAAM,KAAK,KAAK;GAAE;EACzD;EAEA,IAAM,IAAU,IAAY,EAAU,CAAG,IAAI;EAC7C,IAAI,CAAC,GAAS;EAEd,IAAM,IAAQ,EAAQ,SAAS,GACzB,IAAyD;GAC3D,MAAM,EAAQ;GACd,MAAM,EAAQ,QAAQ;GACtB,OAAO,EAAQ,SAAS;GACxB,OAAO,EAAQ;GACf,KAAK,EAAQ;GACb,MAAM;IAAE,KAAK,EAAQ,OAAO;IAAK,GAAI,EAAQ,QAAQ,CAAC;GAAG;EAC7D;EAEA,EAAM,UAAU,EAAG,aAAa,iBAAiB,GAAO,CAAY,CAAC;CACzE,CAAC;AACL;AAWA,SAAgB,EACZ,IAAkD,CAAC,GAC/C;CACJ,IAAM,IAAK,EAAW,GAChB,IACF,EAAQ,gBACN,MAAkB;EAChB,IAAI,OAAO,KAAS,UAAU,OAAO;EACrC,IAAI,KAAQ,OAAO,KAAS,YAAY,SAAS,GAAM;GACnD,IAAM,IAAO,EAAiC;GAC9C,OAAO,OAAO,KAAQ,WAAW,IAAM;EAC3C;EACA,OAAO;CACX;CAEJ,EAAG,iBAAiB,sBAAsB,MAAU;EAChD,EAAM,aAAa,MAAM;EACzB,IAAM,IAAS,EAAW,EAAM,aAAa,IAAI;EAEjD,EAAM,WACD,YAAY;GACT,IAAM,IAAU,MAAM,EAAG,QAAQ,SAAS;IACtC,MAAM;IACN,qBAAqB;GACzB,CAAC;GACD,KAAK,IAAM,KAAU,GACjB,IAAI,EAAO,IAAI,SAAS,CAAM,GAC1B,OAAO,EAAO,MAAM;GAG5B,OAAO,EAAG,QAAQ,WAAW,CAAM;EACvC,EAAA,CAAG,CACP;CACJ,CAAC;AACL;AAMA,SAAgB,IAAmC;CAC/C,IAAM,IAAK,EAAW;CAMtB,EAAG,iBAAiB,YAAY,MAAU;EACtC,AAAI,EAAM,MAAM,SAAS,kBACrB,EAAQ,YAAY;CAE5B,CAAC;AACL"}
|
|
1
|
+
{"version":3,"file":"create-push-handler.js","names":[],"sources":["../../src/sw/create-push-handler.ts"],"sourcesContent":["/**\n * Service-worker context helpers for handling `push` and `notificationclick`\n * events. Import these inside your own `sw.ts` — they expect to run in the\n * service-worker global scope, not in the main thread.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installPushHandler, installNotificationClickHandler } from \"tempest-react-sdk\";\n *\n * installPushHandler({ defaultIcon: \"/icons/Logo.png\" });\n * installNotificationClickHandler();\n */\n\ninterface SwGlobal {\n registration: {\n showNotification(title: string, options?: NotificationOptions): Promise<void>;\n };\n clients: {\n matchAll(options: { type: \"window\"; includeUncontrolled?: boolean }): Promise<\n {\n url: string;\n focused: boolean;\n focus(): Promise<unknown>;\n navigate(url: string): Promise<unknown>;\n }[]\n >;\n openWindow(url: string): Promise<unknown>;\n };\n addEventListener(\n type: \"push\",\n listener: (event: {\n data: { json(): unknown; text(): string } | null;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n addEventListener(\n type: \"notificationclick\",\n listener: (event: {\n notification: { close(): void; data?: unknown };\n action?: string;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n skipWaiting(): Promise<void>;\n}\n\nfunction getSwScope(): SwGlobal {\n return globalThis as unknown as SwGlobal;\n}\n\n/**\n * A notification button as the push payload describes it.\n *\n * `action`, `title` and `icon` are forwarded to the browser as a\n * `NotificationAction`. `url` is not part of that type — the browser would drop\n * it — so {@link installPushHandler} moves it into `data.actionUrls`, where\n * {@link installNotificationClickHandler} reads it back when that button is\n * clicked.\n */\nexport interface PushNotificationAction {\n /** Identifier delivered as `event.action` when the button is clicked. */\n action: string;\n /** Button label. */\n title: string;\n /** Button icon URL. */\n icon?: string;\n /** URL opened when this button is clicked. Falls back to the top-level `url`. */\n url?: string;\n}\n\nexport interface PushPayload {\n title?: string;\n body?: string;\n icon?: string;\n badge?: string;\n image?: string;\n tag?: string;\n /** URL opened when the notification body is clicked. Default: `\"/\"`. */\n url?: string;\n /** Buttons rendered on the notification (browsers show up to `Notification.maxActions`). */\n actions?: PushNotificationAction[];\n /** Keep the notification on screen until the user interacts with it. */\n requireInteraction?: boolean;\n /** Vibration pattern in milliseconds (mobile). */\n vibrate?: number | number[];\n /** Show the notification without sound or vibration. */\n silent?: boolean;\n /** Alert again when a notification with the same `tag` replaces an older one. */\n renotify?: boolean;\n /** Event time in epoch milliseconds, shown by some platforms. */\n timestamp?: number;\n /** Text direction of the title and body. */\n dir?: NotificationDirection;\n /** BCP 47 language tag of the title and body. */\n lang?: string;\n /** Arbitrary extra data forwarded to `event.notification.data`. */\n data?: Record<string, unknown>;\n}\n\n/**\n * `NotificationOptions` plus the members TypeScript's DOM lib leaves out\n * because not every engine implements them. Chromium honours all of them;\n * engines without support ignore the unknown keys.\n */\ntype ExtendedNotificationOptions = NotificationOptions & {\n image?: string;\n actions?: Omit<PushNotificationAction, \"url\">[];\n vibrate?: number | number[];\n renotify?: boolean;\n timestamp?: number;\n};\n\n/**\n * Payload keys copied verbatim onto the notification options when present.\n */\nconst FORWARDED_OPTIONS = [\n \"body\",\n \"image\",\n \"tag\",\n \"requireInteraction\",\n \"vibrate\",\n \"silent\",\n \"renotify\",\n \"timestamp\",\n \"dir\",\n \"lang\",\n] as const satisfies readonly (keyof PushPayload & keyof ExtendedNotificationOptions)[];\n\n/**\n * Build the `showNotification` options from a push payload.\n *\n * Keys the payload omits stay absent instead of being set to `undefined`, so\n * the browser applies its own defaults. Per-action `url`s are collected into\n * `data.actionUrls` (only when at least one action carries one) and stripped\n * from the actions handed to the browser.\n *\n * @param payload - The parsed (and transformed) push payload.\n * @param defaultIcon - Icon used when the payload omits one.\n * @param defaultBadge - Badge used when the payload omits one.\n * @returns The options object for `registration.showNotification`.\n */\nfunction buildNotificationOptions(\n payload: PushPayload,\n defaultIcon: string | undefined,\n defaultBadge: string | undefined,\n): ExtendedNotificationOptions {\n const options: Record<string, unknown> = {\n icon: payload.icon ?? defaultIcon,\n badge: payload.badge ?? defaultBadge,\n };\n for (const key of FORWARDED_OPTIONS) {\n if (payload[key] !== undefined) options[key] = payload[key];\n }\n\n const data: Record<string, unknown> = { url: payload.url ?? \"/\", ...(payload.data ?? {}) };\n if (payload.actions) {\n const actionUrls: Record<string, string> = {};\n options.actions = payload.actions.map(({ url, ...action }) => {\n if (url !== undefined) actionUrls[action.action] = url;\n return action;\n });\n if (Object.keys(actionUrls).length > 0) data.actionUrls = actionUrls;\n }\n options.data = data;\n return options as ExtendedNotificationOptions;\n}\n\nexport interface InstallPushHandlerOptions {\n /** Title used when the payload omits one. */\n defaultTitle?: string;\n /** Icon used when the payload omits one. */\n defaultIcon?: string;\n /** Badge image (mobile). */\n defaultBadge?: string;\n /**\n * Transform the raw payload before showing the notification. Return `null`\n * to suppress the notification entirely (e.g. silent pings).\n */\n transform?: (payload: PushPayload) => PushPayload | null;\n}\n\n/**\n * Install a `push` event listener that parses the payload as JSON (with a\n * plain-text fallback) and shows a notification.\n *\n * Every `NotificationOptions` member the payload carries is forwarded —\n * `actions`, `requireInteraction`, `vibrate`, `silent`, `renotify`,\n * `timestamp`, `dir` and `lang` alongside `body`, `icon`, `badge`, `image` and\n * `tag`. `data` receives the top-level `url` (default `\"/\"`), the payload's\n * own `data`, and `actionUrls` mapping each action id to its `url`.\n */\nexport function installPushHandler(options: InstallPushHandlerOptions = {}): void {\n const sw = getSwScope();\n const { defaultTitle = \"Notificação\", defaultIcon, defaultBadge, transform } = options;\n\n sw.addEventListener(\"push\", (event) => {\n if (!event.data) return;\n\n let raw: PushPayload;\n try {\n raw = event.data.json() as PushPayload;\n } catch {\n raw = { title: defaultTitle, body: event.data.text() };\n }\n\n const payload = transform ? transform(raw) : raw;\n if (!payload) return;\n\n const title = payload.title ?? defaultTitle;\n const notification = buildNotificationOptions(payload, defaultIcon, defaultBadge);\n\n event.waitUntil(sw.registration.showNotification(title, notification));\n });\n}\n\nexport interface InstallNotificationClickHandlerOptions {\n /**\n * Resolve the destination URL from the notification data and the clicked\n * action id (`\"\"` or `undefined` for a click on the body). Default: the\n * action's entry in `data.actionUrls`, else `data.url`, else `\"/\"`.\n */\n resolveUrl?: (data: unknown, action: string | undefined) => string;\n}\n\n/**\n * Default URL resolution for a notification click.\n *\n * @param data - `event.notification.data`, as {@link installPushHandler} wrote it.\n * @param action - `event.action`; empty or `undefined` for a click on the body.\n * @returns The action's URL when it has one, else `data.url`, else `\"/\"`.\n */\nfunction defaultResolveUrl(data: unknown, action: string | undefined): string {\n if (typeof data === \"string\") return data;\n if (!data || typeof data !== \"object\") return \"/\";\n const record = data as Record<string, unknown>;\n if (action && record.actionUrls && typeof record.actionUrls === \"object\") {\n const actionUrl = (record.actionUrls as Record<string, unknown>)[action];\n if (typeof actionUrl === \"string\") return actionUrl;\n }\n return typeof record.url === \"string\" ? record.url : \"/\";\n}\n\n/**\n * Whether an open window client is already showing `target`.\n *\n * `target` is resolved against the client's own URL, so a relative path\n * compares on the client's origin, and the comparison is on the full href.\n * A substring test would let `/events/1` match a client on `/events/10`, and\n * `/` match every client.\n *\n * @param clientUrl - The window client's absolute URL.\n * @param target - The URL the click resolved to, absolute or relative.\n * @returns `true` when both point at the same document.\n */\nfunction clientShowsTarget(clientUrl: string, target: string): boolean {\n try {\n return new URL(target, clientUrl).href === new URL(clientUrl).href;\n } catch {\n return false;\n }\n}\n\n/**\n * Install a `notificationclick` handler that focuses an existing client when\n * possible and falls back to opening a new window.\n *\n * A click on an action button opens that action's URL (see\n * {@link PushNotificationAction.url}); a click on the body, or on an action\n * without one, opens the top-level `url`.\n */\nexport function installNotificationClickHandler(\n options: InstallNotificationClickHandlerOptions = {},\n): void {\n const sw = getSwScope();\n const resolveUrl = options.resolveUrl ?? defaultResolveUrl;\n\n sw.addEventListener(\"notificationclick\", (event) => {\n event.notification.close();\n const target = resolveUrl(event.notification.data, event.action);\n\n event.waitUntil(\n (async () => {\n const clients = await sw.clients.matchAll({\n type: \"window\",\n includeUncontrolled: true,\n });\n for (const client of clients) {\n if (clientShowsTarget(client.url, target)) {\n return client.focus();\n }\n }\n return sw.clients.openWindow(target);\n })(),\n );\n });\n}\n\n/**\n * Install a `message` listener that activates a waiting worker when the host\n * app sends `{ type: \"SKIP_WAITING\" }`.\n */\nexport function installSkipWaitingListener(): void {\n const sw = getSwScope() as SwGlobal & {\n addEventListener(\n type: \"message\",\n listener: (event: { data?: { type?: string } }) => void,\n ): void;\n };\n sw.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"SKIP_WAITING\") {\n void sw.skipWaiting();\n }\n });\n}\n"],"mappings":";AA8CA,SAAS,IAAuB;CAC5B,OAAO;AACX;AAmEA,IAAM,IAAoB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;AAeA,SAAS,EACL,GACA,GACA,GAC2B;CAC3B,IAAM,IAAmC;EACrC,MAAM,EAAQ,QAAQ;EACtB,OAAO,EAAQ,SAAS;CAC5B;CACA,KAAK,IAAM,KAAO,GACd,AAAI,EAAQ,OAAS,KAAA,MAAW,EAAQ,KAAO,EAAQ;CAG3D,IAAM,IAAgC;EAAE,KAAK,EAAQ,OAAO;EAAK,GAAI,EAAQ,QAAQ,CAAC;CAAG;CACzF,IAAI,EAAQ,SAAS;EACjB,IAAM,IAAqC,CAAC;EAK5C,AAJA,EAAQ,UAAU,EAAQ,QAAQ,KAAK,EAAE,QAAK,GAAG,SACzC,MAAQ,KAAA,MAAW,EAAW,EAAO,UAAU,IAC5C,EACV,GACG,OAAO,KAAK,CAAU,CAAC,CAAC,SAAS,MAAG,EAAK,aAAa;CAC9D;CAEA,OADA,EAAQ,OAAO,GACR;AACX;AA0BA,SAAgB,EAAmB,IAAqC,CAAC,GAAS;CAC9E,IAAM,IAAK,EAAW,GAChB,EAAE,kBAAe,eAAe,gBAAa,iBAAc,iBAAc;CAE/E,EAAG,iBAAiB,SAAS,MAAU;EACnC,IAAI,CAAC,EAAM,MAAM;EAEjB,IAAI;EACJ,IAAI;GACA,IAAM,EAAM,KAAK,KAAK;EAC1B,QAAQ;GACJ,IAAM;IAAE,OAAO;IAAc,MAAM,EAAM,KAAK,KAAK;GAAE;EACzD;EAEA,IAAM,IAAU,IAAY,EAAU,CAAG,IAAI;EAC7C,IAAI,CAAC,GAAS;EAEd,IAAM,IAAQ,EAAQ,SAAS,GACzB,IAAe,EAAyB,GAAS,GAAa,CAAY;EAEhF,EAAM,UAAU,EAAG,aAAa,iBAAiB,GAAO,CAAY,CAAC;CACzE,CAAC;AACL;AAkBA,SAAS,EAAkB,GAAe,GAAoC;CAC1E,IAAI,OAAO,KAAS,UAAU,OAAO;CACrC,IAAI,CAAC,KAAQ,OAAO,KAAS,UAAU,OAAO;CAC9C,IAAM,IAAS;CACf,IAAI,KAAU,EAAO,cAAc,OAAO,EAAO,cAAe,UAAU;EACtE,IAAM,IAAa,EAAO,WAAuC;EACjE,IAAI,OAAO,KAAc,UAAU,OAAO;CAC9C;CACA,OAAO,OAAO,EAAO,OAAQ,WAAW,EAAO,MAAM;AACzD;AAcA,SAAS,EAAkB,GAAmB,GAAyB;CACnE,IAAI;EACA,OAAO,IAAI,IAAI,GAAQ,CAAS,CAAC,CAAC,SAAS,IAAI,IAAI,CAAS,CAAC,CAAC;CAClE,QAAQ;EACJ,OAAO;CACX;AACJ;AAUA,SAAgB,EACZ,IAAkD,CAAC,GAC/C;CACJ,IAAM,IAAK,EAAW,GAChB,IAAa,EAAQ,cAAc;CAEzC,EAAG,iBAAiB,sBAAsB,MAAU;EAChD,EAAM,aAAa,MAAM;EACzB,IAAM,IAAS,EAAW,EAAM,aAAa,MAAM,EAAM,MAAM;EAE/D,EAAM,WACD,YAAY;GACT,IAAM,IAAU,MAAM,EAAG,QAAQ,SAAS;IACtC,MAAM;IACN,qBAAqB;GACzB,CAAC;GACD,KAAK,IAAM,KAAU,GACjB,IAAI,EAAkB,EAAO,KAAK,CAAM,GACpC,OAAO,EAAO,MAAM;GAG5B,OAAO,EAAG,QAAQ,WAAW,CAAM;EACvC,EAAA,CAAG,CACP;CACJ,CAAC;AACL;AAMA,SAAgB,IAAmC;CAC/C,IAAM,IAAK,EAAW;CAMtB,EAAG,iBAAiB,YAAY,MAAU;EACtC,AAAI,EAAM,MAAM,SAAS,kBACrB,EAAQ,YAAY;CAE5B,CAAC;AACL"}
|
package/dist/sw.d.ts
CHANGED
|
@@ -110,12 +110,20 @@ export declare interface InstallBackgroundSyncOptions {
|
|
|
110
110
|
/**
|
|
111
111
|
* Install a `notificationclick` handler that focuses an existing client when
|
|
112
112
|
* possible and falls back to opening a new window.
|
|
113
|
+
*
|
|
114
|
+
* A click on an action button opens that action's URL (see
|
|
115
|
+
* {@link PushNotificationAction.url}); a click on the body, or on an action
|
|
116
|
+
* without one, opens the top-level `url`.
|
|
113
117
|
*/
|
|
114
118
|
export declare function installNotificationClickHandler(options?: InstallNotificationClickHandlerOptions): void;
|
|
115
119
|
|
|
116
120
|
export declare interface InstallNotificationClickHandlerOptions {
|
|
117
|
-
/**
|
|
118
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the destination URL from the notification data and the clicked
|
|
123
|
+
* action id (`""` or `undefined` for a click on the body). Default: the
|
|
124
|
+
* action's entry in `data.actionUrls`, else `data.url`, else `"/"`.
|
|
125
|
+
*/
|
|
126
|
+
resolveUrl?: (data: unknown, action: string | undefined) => string;
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
/**
|
|
@@ -154,6 +162,12 @@ export declare interface InstallPrecacheOptions {
|
|
|
154
162
|
/**
|
|
155
163
|
* Install a `push` event listener that parses the payload as JSON (with a
|
|
156
164
|
* plain-text fallback) and shows a notification.
|
|
165
|
+
*
|
|
166
|
+
* Every `NotificationOptions` member the payload carries is forwarded —
|
|
167
|
+
* `actions`, `requireInteraction`, `vibrate`, `silent`, `renotify`,
|
|
168
|
+
* `timestamp`, `dir` and `lang` alongside `body`, `icon`, `badge`, `image` and
|
|
169
|
+
* `tag`. `data` receives the top-level `url` (default `"/"`), the payload's
|
|
170
|
+
* own `data`, and `actionUrls` mapping each action id to its `url`.
|
|
157
171
|
*/
|
|
158
172
|
export declare function installPushHandler(options?: InstallPushHandlerOptions): void;
|
|
159
173
|
|
|
@@ -201,6 +215,26 @@ export declare function installSkipWaitingListener(): void;
|
|
|
201
215
|
* installPushHandler({ defaultIcon: "/icons/Logo.png" });
|
|
202
216
|
* installNotificationClickHandler();
|
|
203
217
|
*/
|
|
218
|
+
/**
|
|
219
|
+
* A notification button as the push payload describes it.
|
|
220
|
+
*
|
|
221
|
+
* `action`, `title` and `icon` are forwarded to the browser as a
|
|
222
|
+
* `NotificationAction`. `url` is not part of that type — the browser would drop
|
|
223
|
+
* it — so {@link installPushHandler} moves it into `data.actionUrls`, where
|
|
224
|
+
* {@link installNotificationClickHandler} reads it back when that button is
|
|
225
|
+
* clicked.
|
|
226
|
+
*/
|
|
227
|
+
export declare interface PushNotificationAction {
|
|
228
|
+
/** Identifier delivered as `event.action` when the button is clicked. */
|
|
229
|
+
action: string;
|
|
230
|
+
/** Button label. */
|
|
231
|
+
title: string;
|
|
232
|
+
/** Button icon URL. */
|
|
233
|
+
icon?: string;
|
|
234
|
+
/** URL opened when this button is clicked. Falls back to the top-level `url`. */
|
|
235
|
+
url?: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
204
238
|
export declare interface PushPayload {
|
|
205
239
|
title?: string;
|
|
206
240
|
body?: string;
|
|
@@ -208,7 +242,24 @@ export declare interface PushPayload {
|
|
|
208
242
|
badge?: string;
|
|
209
243
|
image?: string;
|
|
210
244
|
tag?: string;
|
|
245
|
+
/** URL opened when the notification body is clicked. Default: `"/"`. */
|
|
211
246
|
url?: string;
|
|
247
|
+
/** Buttons rendered on the notification (browsers show up to `Notification.maxActions`). */
|
|
248
|
+
actions?: PushNotificationAction[];
|
|
249
|
+
/** Keep the notification on screen until the user interacts with it. */
|
|
250
|
+
requireInteraction?: boolean;
|
|
251
|
+
/** Vibration pattern in milliseconds (mobile). */
|
|
252
|
+
vibrate?: number | number[];
|
|
253
|
+
/** Show the notification without sound or vibration. */
|
|
254
|
+
silent?: boolean;
|
|
255
|
+
/** Alert again when a notification with the same `tag` replaces an older one. */
|
|
256
|
+
renotify?: boolean;
|
|
257
|
+
/** Event time in epoch milliseconds, shown by some platforms. */
|
|
258
|
+
timestamp?: number;
|
|
259
|
+
/** Text direction of the title and body. */
|
|
260
|
+
dir?: NotificationDirection;
|
|
261
|
+
/** BCP 47 language tag of the title and body. */
|
|
262
|
+
lang?: string;
|
|
212
263
|
/** Arbitrary extra data forwarded to `event.notification.data`. */
|
|
213
264
|
data?: Record<string, unknown>;
|
|
214
265
|
}
|
|
@@ -8007,12 +8007,20 @@ export declare type InstallMethod = "native" | "ios" | "manual" | "none";
|
|
|
8007
8007
|
/**
|
|
8008
8008
|
* Install a `notificationclick` handler that focuses an existing client when
|
|
8009
8009
|
* possible and falls back to opening a new window.
|
|
8010
|
+
*
|
|
8011
|
+
* A click on an action button opens that action's URL (see
|
|
8012
|
+
* {@link PushNotificationAction.url}); a click on the body, or on an action
|
|
8013
|
+
* without one, opens the top-level `url`.
|
|
8010
8014
|
*/
|
|
8011
8015
|
export declare function installNotificationClickHandler(options?: InstallNotificationClickHandlerOptions): void;
|
|
8012
8016
|
|
|
8013
8017
|
export declare interface InstallNotificationClickHandlerOptions {
|
|
8014
|
-
/**
|
|
8015
|
-
|
|
8018
|
+
/**
|
|
8019
|
+
* Resolve the destination URL from the notification data and the clicked
|
|
8020
|
+
* action id (`""` or `undefined` for a click on the body). Default: the
|
|
8021
|
+
* action's entry in `data.actionUrls`, else `data.url`, else `"/"`.
|
|
8022
|
+
*/
|
|
8023
|
+
resolveUrl?: (data: unknown, action: string | undefined) => string;
|
|
8016
8024
|
}
|
|
8017
8025
|
|
|
8018
8026
|
export declare type InstallOutcome = "accepted" | "dismissed" | "unsupported";
|
|
@@ -8053,6 +8061,12 @@ export declare interface InstallPrecacheOptions {
|
|
|
8053
8061
|
/**
|
|
8054
8062
|
* Install a `push` event listener that parses the payload as JSON (with a
|
|
8055
8063
|
* plain-text fallback) and shows a notification.
|
|
8064
|
+
*
|
|
8065
|
+
* Every `NotificationOptions` member the payload carries is forwarded —
|
|
8066
|
+
* `actions`, `requireInteraction`, `vibrate`, `silent`, `renotify`,
|
|
8067
|
+
* `timestamp`, `dir` and `lang` alongside `body`, `icon`, `badge`, `image` and
|
|
8068
|
+
* `tag`. `data` receives the top-level `url` (default `"/"`), the payload's
|
|
8069
|
+
* own `data`, and `actionUrls` mapping each action id to its `url`.
|
|
8056
8070
|
*/
|
|
8057
8071
|
export declare function installPushHandler(options?: InstallPushHandlerOptions): void;
|
|
8058
8072
|
|
|
@@ -11692,6 +11706,26 @@ export declare const PUSH_TO_TALK_KEYS: readonly PushToTalkKey[];
|
|
|
11692
11706
|
* installPushHandler({ defaultIcon: "/icons/Logo.png" });
|
|
11693
11707
|
* installNotificationClickHandler();
|
|
11694
11708
|
*/
|
|
11709
|
+
/**
|
|
11710
|
+
* A notification button as the push payload describes it.
|
|
11711
|
+
*
|
|
11712
|
+
* `action`, `title` and `icon` are forwarded to the browser as a
|
|
11713
|
+
* `NotificationAction`. `url` is not part of that type — the browser would drop
|
|
11714
|
+
* it — so {@link installPushHandler} moves it into `data.actionUrls`, where
|
|
11715
|
+
* {@link installNotificationClickHandler} reads it back when that button is
|
|
11716
|
+
* clicked.
|
|
11717
|
+
*/
|
|
11718
|
+
export declare interface PushNotificationAction {
|
|
11719
|
+
/** Identifier delivered as `event.action` when the button is clicked. */
|
|
11720
|
+
action: string;
|
|
11721
|
+
/** Button label. */
|
|
11722
|
+
title: string;
|
|
11723
|
+
/** Button icon URL. */
|
|
11724
|
+
icon?: string;
|
|
11725
|
+
/** URL opened when this button is clicked. Falls back to the top-level `url`. */
|
|
11726
|
+
url?: string;
|
|
11727
|
+
}
|
|
11728
|
+
|
|
11695
11729
|
export declare interface PushPayload {
|
|
11696
11730
|
title?: string;
|
|
11697
11731
|
body?: string;
|
|
@@ -11699,7 +11733,24 @@ export declare interface PushPayload {
|
|
|
11699
11733
|
badge?: string;
|
|
11700
11734
|
image?: string;
|
|
11701
11735
|
tag?: string;
|
|
11736
|
+
/** URL opened when the notification body is clicked. Default: `"/"`. */
|
|
11702
11737
|
url?: string;
|
|
11738
|
+
/** Buttons rendered on the notification (browsers show up to `Notification.maxActions`). */
|
|
11739
|
+
actions?: PushNotificationAction[];
|
|
11740
|
+
/** Keep the notification on screen until the user interacts with it. */
|
|
11741
|
+
requireInteraction?: boolean;
|
|
11742
|
+
/** Vibration pattern in milliseconds (mobile). */
|
|
11743
|
+
vibrate?: number | number[];
|
|
11744
|
+
/** Show the notification without sound or vibration. */
|
|
11745
|
+
silent?: boolean;
|
|
11746
|
+
/** Alert again when a notification with the same `tag` replaces an older one. */
|
|
11747
|
+
renotify?: boolean;
|
|
11748
|
+
/** Event time in epoch milliseconds, shown by some platforms. */
|
|
11749
|
+
timestamp?: number;
|
|
11750
|
+
/** Text direction of the title and body. */
|
|
11751
|
+
dir?: NotificationDirection;
|
|
11752
|
+
/** BCP 47 language tag of the title and body. */
|
|
11753
|
+
lang?: string;
|
|
11703
11754
|
/** Arbitrary extra data forwarded to `event.notification.data`. */
|
|
11704
11755
|
data?: Record<string, unknown>;
|
|
11705
11756
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./exceptions.cjs"),t=require("../types.cjs");
|
|
1
|
+
const e=require("./exceptions.cjs"),t=require("../types.cjs");var n=new Uint8Array(new Uint32Array([1]).buffer)[0]===1;function r(t,n){if(typeof OffscreenCanvas<`u`)return new OffscreenCanvas(t,n);if(typeof document<`u`){let e=document.createElement(`canvas`);return e.width=t,e.height=n,e}throw new e.ImageLoadError(`No canvas implementation available in this environment.`)}function i(t,n={}){let r=t.getContext(`2d`,n);if(r===null)throw new e.ImageLoadError(`Failed to obtain 2D rendering context.`);return r}function a(e){return o(e).image}function o(r){let{data:i,width:a,height:o}=r;if(i.length!==a*o*4)throw new e.ImageLoadError(`Unexpected ImageData length ${i.length} for ${a}x${o} (expected ${a*o*4}).`);let s=a*o,c=new Uint8Array(s*3);if(n&&i.byteOffset%4==0){let e=new Uint32Array(i.buffer,i.byteOffset,s),n=-1;for(let t=0,r=0;t<s;t++,r+=3){let i=e[t];c[r]=i,c[r+1]=i>>>8,c[r+2]=i>>>16,n&=i}return{image:new t.RGBImage(c,a,o),opaque:n>>>24==255}}let l=255;for(let e=0,t=0;e<i.length;e+=4,t+=3)c[t]=i[e],c[t+1]=i[e+1],c[t+2]=i[e+2],l&=i[e+3];return{image:new t.RGBImage(c,a,o),opaque:l===255}}function s(e){let t=e.width*e.height,r=new Uint8ClampedArray(t*4),i=e.data;if(n){let n=new Uint32Array(r.buffer);for(let e=0,r=0;e<t;e++,r+=3)n[e]=(i[r]|i[r+1]<<8|i[r+2]<<16|4278190080)>>>0;return new ImageData(r,e.width,e.height)}for(let t=0,n=0;t<e.data.length;t+=3,n+=4)r[n]=e.data[t],r[n+1]=e.data[t+1],r[n+2]=e.data[t+2],r[n+3]=255;return new ImageData(r,e.width,e.height)}exports.createCanvas=r,exports.get2DContext=i,exports.imageDataToRGB=a,exports.imageDataToRGBChecked=o,exports.rgbToImageData=s;
|
|
2
2
|
//# sourceMappingURL=canvas.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.cjs","names":[],"sources":["../../../src/vision/core/canvas.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Canvas plumbing shared by `io/image.ts` and `preprocess/image.ts`.\n *\n * Browsers and workers expose two canvas types (`HTMLCanvasElement` and\n * `OffscreenCanvas`) with slightly different surfaces. These helpers pick the\n * right one for the current environment, return a unified 2D context, and\n * convert between RGBA `ImageData` and the SDK's canonical HWC RGB\n * `RGBImage`.\n */\n\nimport { ImageLoadError } from \"./exceptions\";\nimport { RGBImage } from \"../types\";\n\nexport type Canvas2D = HTMLCanvasElement | OffscreenCanvas;\nexport type Context2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n/** Allocate a `Canvas2D`, preferring `OffscreenCanvas` when available. */\nexport function createCanvas(width: number, height: number): Canvas2D {\n if (typeof OffscreenCanvas !== \"undefined\") {\n return new OffscreenCanvas(width, height);\n }\n if (typeof document !== \"undefined\") {\n const c = document.createElement(\"canvas\");\n c.width = width;\n c.height = height;\n return c;\n }\n throw new ImageLoadError(\"No canvas implementation available in this environment.\");\n}\n\n/**\n * Get a 2D context from a canvas, throwing a typed error if the browser refuses.\n *\n * @param canvas The canvas to obtain a context for.\n * @param options Context attributes, forwarded verbatim. `willReadFrequently`\n * tells the browser the canvas is read back with `getImageData` repeatedly,\n * which moves the backing store to software: faster readback, slower\n * compositing. Whether it pays depends on the surface, so it is the caller's\n * call rather than a default here.\n */\nexport function get2DContext(\n canvas: Canvas2D,\n options: CanvasRenderingContext2DSettings = {},\n): Context2D {\n const ctx = canvas.getContext(\"2d\", options) as Context2D | null;\n if (ctx === null) {\n throw new ImageLoadError(\"Failed to obtain 2D rendering context.\");\n }\n return ctx;\n}\n\n/** Convert RGBA `ImageData` into the canonical HWC RGB `RGBImage`. */\nexport function imageDataToRGB(imageData: ImageData): RGBImage {\n const { data, width, height } = imageData;\n if (data.length !== width * height * 4) {\n throw new ImageLoadError(\n `Unexpected ImageData length ${data.length} for ${width}x${height} (expected ${\n width * height * 4\n }).`,\n );\n }\n const rgb = new Uint8Array(
|
|
1
|
+
{"version":3,"file":"canvas.cjs","names":[],"sources":["../../../src/vision/core/canvas.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Canvas plumbing shared by `io/image.ts` and `preprocess/image.ts`.\n *\n * Browsers and workers expose two canvas types (`HTMLCanvasElement` and\n * `OffscreenCanvas`) with slightly different surfaces. These helpers pick the\n * right one for the current environment, return a unified 2D context, and\n * convert between RGBA `ImageData` and the SDK's canonical HWC RGB\n * `RGBImage`.\n */\n\nimport { ImageLoadError } from \"./exceptions\";\nimport { RGBImage } from \"../types\";\n\nexport type Canvas2D = HTMLCanvasElement | OffscreenCanvas;\n\n/**\n * Whether typed-array words are stored least-significant byte first.\n *\n * The pixel conversions below move one RGBA pixel as one `Uint32` — about a\n * quarter fewer milliseconds per 1080p frame than four byte accesses — which\n * only maps `R` to the low byte on a little-endian platform. Every browser\n * engine in use is little-endian; the byte-wise loops stay as the fallback so\n * correctness never rests on that.\n */\nconst LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;\nexport type Context2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n/** Allocate a `Canvas2D`, preferring `OffscreenCanvas` when available. */\nexport function createCanvas(width: number, height: number): Canvas2D {\n if (typeof OffscreenCanvas !== \"undefined\") {\n return new OffscreenCanvas(width, height);\n }\n if (typeof document !== \"undefined\") {\n const c = document.createElement(\"canvas\");\n c.width = width;\n c.height = height;\n return c;\n }\n throw new ImageLoadError(\"No canvas implementation available in this environment.\");\n}\n\n/**\n * Get a 2D context from a canvas, throwing a typed error if the browser refuses.\n *\n * @param canvas The canvas to obtain a context for.\n * @param options Context attributes, forwarded verbatim. `willReadFrequently`\n * tells the browser the canvas is read back with `getImageData` repeatedly,\n * which moves the backing store to software: faster readback, slower\n * compositing. Whether it pays depends on the surface, so it is the caller's\n * call rather than a default here.\n */\nexport function get2DContext(\n canvas: Canvas2D,\n options: CanvasRenderingContext2DSettings = {},\n): Context2D {\n const ctx = canvas.getContext(\"2d\", options) as Context2D | null;\n if (ctx === null) {\n throw new ImageLoadError(\"Failed to obtain 2D rendering context.\");\n }\n return ctx;\n}\n\n/** Convert RGBA `ImageData` into the canonical HWC RGB `RGBImage`. */\nexport function imageDataToRGB(imageData: ImageData): RGBImage {\n return imageDataToRGBChecked(imageData).image;\n}\n\n/** An {@link RGBImage} plus whether the RGBA it came from was fully opaque. */\nexport interface CheckedRGB {\n readonly image: RGBImage;\n /** `true` when every source pixel had alpha 255. */\n readonly opaque: boolean;\n}\n\n/**\n * Convert RGBA `ImageData` into an `RGBImage`, noting whether alpha was all 255.\n *\n * Dropping alpha is lossy only when some pixel was translucent: the RGB kept is\n * the un-premultiplied colour, which {@link rgbToImageData} later paints back as\n * fully opaque. A canvas still holding the original RGBA composites that pixel\n * differently, so it can stand in for the `RGBImage` only when nothing was\n * translucent — which is what `opaque` reports. The check rides the copy loop\n * that already reads every alpha byte, so it costs one AND per pixel. The loop\n * reads a whole pixel as one `Uint32` where {@link LITTLE_ENDIAN} allows.\n *\n * @param imageData Source pixels.\n * @returns The packed RGB image and the opacity verdict.\n * @throws {@link ImageLoadError} if the buffer length does not match the size.\n */\nexport function imageDataToRGBChecked(imageData: ImageData): CheckedRGB {\n const { data, width, height } = imageData;\n if (data.length !== width * height * 4) {\n throw new ImageLoadError(\n `Unexpected ImageData length ${data.length} for ${width}x${height} (expected ${\n width * height * 4\n }).`,\n );\n }\n const pixels = width * height;\n const rgb = new Uint8Array(pixels * 3);\n if (LITTLE_ENDIAN && data.byteOffset % 4 === 0) {\n const words = new Uint32Array(data.buffer, data.byteOffset, pixels);\n let alpha = -1;\n for (let p = 0, j = 0; p < pixels; p++, j += 3) {\n const word = words[p] as number;\n rgb[j] = word;\n rgb[j + 1] = word >>> 8;\n rgb[j + 2] = word >>> 16;\n alpha &= word;\n }\n return { image: new RGBImage(rgb, width, height), opaque: alpha >>> 24 === 255 };\n }\n let alpha = 255;\n for (let i = 0, j = 0; i < data.length; i += 4, j += 3) {\n rgb[j] = data[i] as number;\n rgb[j + 1] = data[i + 1] as number;\n rgb[j + 2] = data[i + 2] as number;\n alpha &= data[i + 3] as number;\n }\n return { image: new RGBImage(rgb, width, height), opaque: alpha === 255 };\n}\n\n/**\n * Convert an `RGBImage` into RGBA `ImageData` (alpha forced to 255).\n *\n * Writes one 32-bit word per pixel where the platform allows — see\n * {@link LITTLE_ENDIAN} — instead of four separate byte stores.\n */\nexport function rgbToImageData(image: RGBImage): ImageData {\n const pixels = image.width * image.height;\n const data = new Uint8ClampedArray(pixels * 4);\n const src = image.data;\n if (LITTLE_ENDIAN) {\n const words = new Uint32Array(data.buffer);\n for (let p = 0, i = 0; p < pixels; p++, i += 3) {\n words[p] =\n ((src[i] as number) |\n ((src[i + 1] as number) << 8) |\n ((src[i + 2] as number) << 16) |\n 0xff000000) >>>\n 0;\n }\n return new ImageData(data, image.width, image.height);\n }\n for (let i = 0, j = 0; i < image.data.length; i += 3, j += 4) {\n data[j] = image.data[i] as number;\n data[j + 1] = image.data[i + 1] as number;\n data[j + 2] = image.data[i + 2] as number;\n data[j + 3] = 255;\n }\n return new ImageData(data, image.width, image.height);\n}\n"],"mappings":"8DAyBA,IAAM,EAAgB,IAAI,WAAW,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAO,EAIzE,SAAgB,EAAa,EAAe,EAA0B,CAClE,GAAI,OAAO,gBAAoB,IAC3B,OAAO,IAAI,gBAAgB,EAAO,CAAM,EAE5C,GAAI,OAAO,SAAa,IAAa,CACjC,IAAM,EAAI,SAAS,cAAc,QAAQ,EAGzC,MAFA,GAAE,MAAQ,EACV,EAAE,OAAS,EACJ,CACX,CACA,MAAM,IAAI,EAAA,eAAe,yDAAyD,CACtF,CAYA,SAAgB,EACZ,EACA,EAA4C,CAAC,EACpC,CACT,IAAM,EAAM,EAAO,WAAW,KAAM,CAAO,EAC3C,GAAI,IAAQ,KACR,MAAM,IAAI,EAAA,eAAe,wCAAwC,EAErE,OAAO,CACX,CAGA,SAAgB,EAAe,EAAgC,CAC3D,OAAO,EAAsB,CAAS,CAAC,CAAC,KAC5C,CAwBA,SAAgB,EAAsB,EAAkC,CACpE,GAAM,CAAE,OAAM,QAAO,UAAW,EAChC,GAAI,EAAK,SAAW,EAAQ,EAAS,EACjC,MAAM,IAAI,EAAA,eACN,+BAA+B,EAAK,OAAO,OAAO,EAAM,GAAG,EAAO,aAC9D,EAAQ,EAAS,EACpB,GACL,EAEJ,IAAM,EAAS,EAAQ,EACjB,EAAM,IAAI,WAAW,EAAS,CAAC,EACrC,GAAI,GAAiB,EAAK,WAAa,GAAM,EAAG,CAC5C,IAAM,EAAQ,IAAI,YAAY,EAAK,OAAQ,EAAK,WAAY,CAAM,EAC9D,EAAQ,GACZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,EAAI,EAAQ,IAAK,GAAK,EAAG,CAC5C,IAAM,EAAO,EAAM,GACnB,EAAI,GAAK,EACT,EAAI,EAAI,GAAK,IAAS,EACtB,EAAI,EAAI,GAAK,IAAS,GACtB,GAAS,CACb,CACA,MAAO,CAAE,MAAO,IAAI,EAAA,SAAS,EAAK,EAAO,CAAM,EAAG,OAAQ,IAAU,IAAO,GAAI,CACnF,CACA,IAAI,EAAQ,IACZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,EAAI,EAAK,OAAQ,GAAK,EAAG,GAAK,EACjD,EAAI,GAAK,EAAK,GACd,EAAI,EAAI,GAAK,EAAK,EAAI,GACtB,EAAI,EAAI,GAAK,EAAK,EAAI,GACtB,GAAS,EAAK,EAAI,GAEtB,MAAO,CAAE,MAAO,IAAI,EAAA,SAAS,EAAK,EAAO,CAAM,EAAG,OAAQ,IAAU,GAAI,CAC5E,CAQA,SAAgB,EAAe,EAA4B,CACvD,IAAM,EAAS,EAAM,MAAQ,EAAM,OAC7B,EAAO,IAAI,kBAAkB,EAAS,CAAC,EACvC,EAAM,EAAM,KAClB,GAAI,EAAe,CACf,IAAM,EAAQ,IAAI,YAAY,EAAK,MAAM,EACzC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,EAAI,EAAQ,IAAK,GAAK,EACzC,EAAM,IACA,EAAI,GACA,EAAI,EAAI,IAAiB,EACzB,EAAI,EAAI,IAAiB,GAC3B,cACJ,EAER,OAAO,IAAI,UAAU,EAAM,EAAM,MAAO,EAAM,MAAM,CACxD,CACA,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,EAAI,EAAM,KAAK,OAAQ,GAAK,EAAG,GAAK,EACvD,EAAK,GAAK,EAAM,KAAK,GACrB,EAAK,EAAI,GAAK,EAAM,KAAK,EAAI,GAC7B,EAAK,EAAI,GAAK,EAAM,KAAK,EAAI,GAC7B,EAAK,EAAI,GAAK,IAElB,OAAO,IAAI,UAAU,EAAM,EAAM,MAAO,EAAM,MAAM,CACxD"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ImageLoadError as e } from "./exceptions.js";
|
|
2
2
|
import { RGBImage as t } from "../types.js";
|
|
3
3
|
//#region src/vision/core/canvas.ts
|
|
4
|
-
|
|
4
|
+
var n = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
|
|
5
|
+
function r(t, n) {
|
|
5
6
|
if (typeof OffscreenCanvas < "u") return new OffscreenCanvas(t, n);
|
|
6
7
|
if (typeof document < "u") {
|
|
7
8
|
let e = document.createElement("canvas");
|
|
@@ -9,24 +10,47 @@ function n(t, n) {
|
|
|
9
10
|
}
|
|
10
11
|
throw new e("No canvas implementation available in this environment.");
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
+
function i(t, n = {}) {
|
|
13
14
|
let r = t.getContext("2d", n);
|
|
14
15
|
if (r === null) throw new e("Failed to obtain 2D rendering context.");
|
|
15
16
|
return r;
|
|
16
17
|
}
|
|
17
|
-
function i(n) {
|
|
18
|
-
let { data: r, width: i, height: a } = n;
|
|
19
|
-
if (r.length !== i * a * 4) throw new e(`Unexpected ImageData length ${r.length} for ${i}x${a} (expected ${i * a * 4}).`);
|
|
20
|
-
let o = new Uint8Array(i * a * 3);
|
|
21
|
-
for (let e = 0, t = 0; e < r.length; e += 4, t += 3) o[t] = r[e], o[t + 1] = r[e + 1], o[t + 2] = r[e + 2];
|
|
22
|
-
return new t(o, i, a);
|
|
23
|
-
}
|
|
24
18
|
function a(e) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
return o(e).image;
|
|
20
|
+
}
|
|
21
|
+
function o(r) {
|
|
22
|
+
let { data: i, width: a, height: o } = r;
|
|
23
|
+
if (i.length !== a * o * 4) throw new e(`Unexpected ImageData length ${i.length} for ${a}x${o} (expected ${a * o * 4}).`);
|
|
24
|
+
let s = a * o, c = new Uint8Array(s * 3);
|
|
25
|
+
if (n && i.byteOffset % 4 == 0) {
|
|
26
|
+
let e = new Uint32Array(i.buffer, i.byteOffset, s), n = -1;
|
|
27
|
+
for (let t = 0, r = 0; t < s; t++, r += 3) {
|
|
28
|
+
let i = e[t];
|
|
29
|
+
c[r] = i, c[r + 1] = i >>> 8, c[r + 2] = i >>> 16, n &= i;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
image: new t(c, a, o),
|
|
33
|
+
opaque: n >>> 24 == 255
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
let l = 255;
|
|
37
|
+
for (let e = 0, t = 0; e < i.length; e += 4, t += 3) c[t] = i[e], c[t + 1] = i[e + 1], c[t + 2] = i[e + 2], l &= i[e + 3];
|
|
38
|
+
return {
|
|
39
|
+
image: new t(c, a, o),
|
|
40
|
+
opaque: l === 255
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function s(e) {
|
|
44
|
+
let t = e.width * e.height, r = new Uint8ClampedArray(t * 4), i = e.data;
|
|
45
|
+
if (n) {
|
|
46
|
+
let n = new Uint32Array(r.buffer);
|
|
47
|
+
for (let e = 0, r = 0; e < t; e++, r += 3) n[e] = (i[r] | i[r + 1] << 8 | i[r + 2] << 16 | 4278190080) >>> 0;
|
|
48
|
+
return new ImageData(r, e.width, e.height);
|
|
49
|
+
}
|
|
50
|
+
for (let t = 0, n = 0; t < e.data.length; t += 3, n += 4) r[n] = e.data[t], r[n + 1] = e.data[t + 1], r[n + 2] = e.data[t + 2], r[n + 3] = 255;
|
|
51
|
+
return new ImageData(r, e.width, e.height);
|
|
28
52
|
}
|
|
29
53
|
//#endregion
|
|
30
|
-
export {
|
|
54
|
+
export { r as createCanvas, i as get2DContext, a as imageDataToRGB, o as imageDataToRGBChecked, s as rgbToImageData };
|
|
31
55
|
|
|
32
56
|
//# sourceMappingURL=canvas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.js","names":[],"sources":["../../../src/vision/core/canvas.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Canvas plumbing shared by `io/image.ts` and `preprocess/image.ts`.\n *\n * Browsers and workers expose two canvas types (`HTMLCanvasElement` and\n * `OffscreenCanvas`) with slightly different surfaces. These helpers pick the\n * right one for the current environment, return a unified 2D context, and\n * convert between RGBA `ImageData` and the SDK's canonical HWC RGB\n * `RGBImage`.\n */\n\nimport { ImageLoadError } from \"./exceptions\";\nimport { RGBImage } from \"../types\";\n\nexport type Canvas2D = HTMLCanvasElement | OffscreenCanvas;\nexport type Context2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n/** Allocate a `Canvas2D`, preferring `OffscreenCanvas` when available. */\nexport function createCanvas(width: number, height: number): Canvas2D {\n if (typeof OffscreenCanvas !== \"undefined\") {\n return new OffscreenCanvas(width, height);\n }\n if (typeof document !== \"undefined\") {\n const c = document.createElement(\"canvas\");\n c.width = width;\n c.height = height;\n return c;\n }\n throw new ImageLoadError(\"No canvas implementation available in this environment.\");\n}\n\n/**\n * Get a 2D context from a canvas, throwing a typed error if the browser refuses.\n *\n * @param canvas The canvas to obtain a context for.\n * @param options Context attributes, forwarded verbatim. `willReadFrequently`\n * tells the browser the canvas is read back with `getImageData` repeatedly,\n * which moves the backing store to software: faster readback, slower\n * compositing. Whether it pays depends on the surface, so it is the caller's\n * call rather than a default here.\n */\nexport function get2DContext(\n canvas: Canvas2D,\n options: CanvasRenderingContext2DSettings = {},\n): Context2D {\n const ctx = canvas.getContext(\"2d\", options) as Context2D | null;\n if (ctx === null) {\n throw new ImageLoadError(\"Failed to obtain 2D rendering context.\");\n }\n return ctx;\n}\n\n/** Convert RGBA `ImageData` into the canonical HWC RGB `RGBImage`. */\nexport function imageDataToRGB(imageData: ImageData): RGBImage {\n const { data, width, height } = imageData;\n if (data.length !== width * height * 4) {\n throw new ImageLoadError(\n `Unexpected ImageData length ${data.length} for ${width}x${height} (expected ${\n width * height * 4\n }).`,\n );\n }\n const rgb = new Uint8Array(
|
|
1
|
+
{"version":3,"file":"canvas.js","names":[],"sources":["../../../src/vision/core/canvas.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Canvas plumbing shared by `io/image.ts` and `preprocess/image.ts`.\n *\n * Browsers and workers expose two canvas types (`HTMLCanvasElement` and\n * `OffscreenCanvas`) with slightly different surfaces. These helpers pick the\n * right one for the current environment, return a unified 2D context, and\n * convert between RGBA `ImageData` and the SDK's canonical HWC RGB\n * `RGBImage`.\n */\n\nimport { ImageLoadError } from \"./exceptions\";\nimport { RGBImage } from \"../types\";\n\nexport type Canvas2D = HTMLCanvasElement | OffscreenCanvas;\n\n/**\n * Whether typed-array words are stored least-significant byte first.\n *\n * The pixel conversions below move one RGBA pixel as one `Uint32` — about a\n * quarter fewer milliseconds per 1080p frame than four byte accesses — which\n * only maps `R` to the low byte on a little-endian platform. Every browser\n * engine in use is little-endian; the byte-wise loops stay as the fallback so\n * correctness never rests on that.\n */\nconst LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;\nexport type Context2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n/** Allocate a `Canvas2D`, preferring `OffscreenCanvas` when available. */\nexport function createCanvas(width: number, height: number): Canvas2D {\n if (typeof OffscreenCanvas !== \"undefined\") {\n return new OffscreenCanvas(width, height);\n }\n if (typeof document !== \"undefined\") {\n const c = document.createElement(\"canvas\");\n c.width = width;\n c.height = height;\n return c;\n }\n throw new ImageLoadError(\"No canvas implementation available in this environment.\");\n}\n\n/**\n * Get a 2D context from a canvas, throwing a typed error if the browser refuses.\n *\n * @param canvas The canvas to obtain a context for.\n * @param options Context attributes, forwarded verbatim. `willReadFrequently`\n * tells the browser the canvas is read back with `getImageData` repeatedly,\n * which moves the backing store to software: faster readback, slower\n * compositing. Whether it pays depends on the surface, so it is the caller's\n * call rather than a default here.\n */\nexport function get2DContext(\n canvas: Canvas2D,\n options: CanvasRenderingContext2DSettings = {},\n): Context2D {\n const ctx = canvas.getContext(\"2d\", options) as Context2D | null;\n if (ctx === null) {\n throw new ImageLoadError(\"Failed to obtain 2D rendering context.\");\n }\n return ctx;\n}\n\n/** Convert RGBA `ImageData` into the canonical HWC RGB `RGBImage`. */\nexport function imageDataToRGB(imageData: ImageData): RGBImage {\n return imageDataToRGBChecked(imageData).image;\n}\n\n/** An {@link RGBImage} plus whether the RGBA it came from was fully opaque. */\nexport interface CheckedRGB {\n readonly image: RGBImage;\n /** `true` when every source pixel had alpha 255. */\n readonly opaque: boolean;\n}\n\n/**\n * Convert RGBA `ImageData` into an `RGBImage`, noting whether alpha was all 255.\n *\n * Dropping alpha is lossy only when some pixel was translucent: the RGB kept is\n * the un-premultiplied colour, which {@link rgbToImageData} later paints back as\n * fully opaque. A canvas still holding the original RGBA composites that pixel\n * differently, so it can stand in for the `RGBImage` only when nothing was\n * translucent — which is what `opaque` reports. The check rides the copy loop\n * that already reads every alpha byte, so it costs one AND per pixel. The loop\n * reads a whole pixel as one `Uint32` where {@link LITTLE_ENDIAN} allows.\n *\n * @param imageData Source pixels.\n * @returns The packed RGB image and the opacity verdict.\n * @throws {@link ImageLoadError} if the buffer length does not match the size.\n */\nexport function imageDataToRGBChecked(imageData: ImageData): CheckedRGB {\n const { data, width, height } = imageData;\n if (data.length !== width * height * 4) {\n throw new ImageLoadError(\n `Unexpected ImageData length ${data.length} for ${width}x${height} (expected ${\n width * height * 4\n }).`,\n );\n }\n const pixels = width * height;\n const rgb = new Uint8Array(pixels * 3);\n if (LITTLE_ENDIAN && data.byteOffset % 4 === 0) {\n const words = new Uint32Array(data.buffer, data.byteOffset, pixels);\n let alpha = -1;\n for (let p = 0, j = 0; p < pixels; p++, j += 3) {\n const word = words[p] as number;\n rgb[j] = word;\n rgb[j + 1] = word >>> 8;\n rgb[j + 2] = word >>> 16;\n alpha &= word;\n }\n return { image: new RGBImage(rgb, width, height), opaque: alpha >>> 24 === 255 };\n }\n let alpha = 255;\n for (let i = 0, j = 0; i < data.length; i += 4, j += 3) {\n rgb[j] = data[i] as number;\n rgb[j + 1] = data[i + 1] as number;\n rgb[j + 2] = data[i + 2] as number;\n alpha &= data[i + 3] as number;\n }\n return { image: new RGBImage(rgb, width, height), opaque: alpha === 255 };\n}\n\n/**\n * Convert an `RGBImage` into RGBA `ImageData` (alpha forced to 255).\n *\n * Writes one 32-bit word per pixel where the platform allows — see\n * {@link LITTLE_ENDIAN} — instead of four separate byte stores.\n */\nexport function rgbToImageData(image: RGBImage): ImageData {\n const pixels = image.width * image.height;\n const data = new Uint8ClampedArray(pixels * 4);\n const src = image.data;\n if (LITTLE_ENDIAN) {\n const words = new Uint32Array(data.buffer);\n for (let p = 0, i = 0; p < pixels; p++, i += 3) {\n words[p] =\n ((src[i] as number) |\n ((src[i + 1] as number) << 8) |\n ((src[i + 2] as number) << 16) |\n 0xff000000) >>>\n 0;\n }\n return new ImageData(data, image.width, image.height);\n }\n for (let i = 0, j = 0; i < image.data.length; i += 3, j += 4) {\n data[j] = image.data[i] as number;\n data[j + 1] = image.data[i + 1] as number;\n data[j + 2] = image.data[i + 2] as number;\n data[j + 3] = 255;\n }\n return new ImageData(data, image.width, image.height);\n}\n"],"mappings":";;;AAyBA,IAAM,IAAgB,IAAI,WAAW,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO;AAIzE,SAAgB,EAAa,GAAe,GAA0B;CAClE,IAAI,OAAO,kBAAoB,KAC3B,OAAO,IAAI,gBAAgB,GAAO,CAAM;CAE5C,IAAI,OAAO,WAAa,KAAa;EACjC,IAAM,IAAI,SAAS,cAAc,QAAQ;EAGzC,OAFA,EAAE,QAAQ,GACV,EAAE,SAAS,GACJ;CACX;CACA,MAAM,IAAI,EAAe,yDAAyD;AACtF;AAYA,SAAgB,EACZ,GACA,IAA4C,CAAC,GACpC;CACT,IAAM,IAAM,EAAO,WAAW,MAAM,CAAO;CAC3C,IAAI,MAAQ,MACR,MAAM,IAAI,EAAe,wCAAwC;CAErE,OAAO;AACX;AAGA,SAAgB,EAAe,GAAgC;CAC3D,OAAO,EAAsB,CAAS,CAAC,CAAC;AAC5C;AAwBA,SAAgB,EAAsB,GAAkC;CACpE,IAAM,EAAE,SAAM,UAAO,cAAW;CAChC,IAAI,EAAK,WAAW,IAAQ,IAAS,GACjC,MAAM,IAAI,EACN,+BAA+B,EAAK,OAAO,OAAO,EAAM,GAAG,EAAO,aAC9D,IAAQ,IAAS,EACpB,GACL;CAEJ,IAAM,IAAS,IAAQ,GACjB,IAAM,IAAI,WAAW,IAAS,CAAC;CACrC,IAAI,KAAiB,EAAK,aAAa,KAAM,GAAG;EAC5C,IAAM,IAAQ,IAAI,YAAY,EAAK,QAAQ,EAAK,YAAY,CAAM,GAC9D,IAAQ;EACZ,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAQ,KAAK,KAAK,GAAG;GAC5C,IAAM,IAAO,EAAM;GAInB,AAHA,EAAI,KAAK,GACT,EAAI,IAAI,KAAK,MAAS,GACtB,EAAI,IAAI,KAAK,MAAS,IACtB,KAAS;EACb;EACA,OAAO;GAAE,OAAO,IAAI,EAAS,GAAK,GAAO,CAAM;GAAG,QAAQ,MAAU,MAAO;EAAI;CACnF;CACA,IAAI,IAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,EAAK,QAAQ,KAAK,GAAG,KAAK,GAIjD,AAHA,EAAI,KAAK,EAAK,IACd,EAAI,IAAI,KAAK,EAAK,IAAI,IACtB,EAAI,IAAI,KAAK,EAAK,IAAI,IACtB,KAAS,EAAK,IAAI;CAEtB,OAAO;EAAE,OAAO,IAAI,EAAS,GAAK,GAAO,CAAM;EAAG,QAAQ,MAAU;CAAI;AAC5E;AAQA,SAAgB,EAAe,GAA4B;CACvD,IAAM,IAAS,EAAM,QAAQ,EAAM,QAC7B,IAAO,IAAI,kBAAkB,IAAS,CAAC,GACvC,IAAM,EAAM;CAClB,IAAI,GAAe;EACf,IAAM,IAAQ,IAAI,YAAY,EAAK,MAAM;EACzC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAQ,KAAK,KAAK,GACzC,EAAM,MACA,EAAI,KACA,EAAI,IAAI,MAAiB,IACzB,EAAI,IAAI,MAAiB,KAC3B,gBACJ;EAER,OAAO,IAAI,UAAU,GAAM,EAAM,OAAO,EAAM,MAAM;CACxD;CACA,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,EAAM,KAAK,QAAQ,KAAK,GAAG,KAAK,GAIvD,AAHA,EAAK,KAAK,EAAM,KAAK,IACrB,EAAK,IAAI,KAAK,EAAM,KAAK,IAAI,IAC7B,EAAK,IAAI,KAAK,EAAM,KAAK,IAAI,IAC7B,EAAK,IAAI,KAAK;CAElB,OAAO,IAAI,UAAU,GAAM,EAAM,OAAO,EAAM,MAAM;AACxD"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e={1:`float32`,2:`uint8`,3:`int8`,4:`uint16`,5:`int16`,6:`int32`,7:`int64`,9:`bool`,10:`float16`,11:`float64`,12:`uint32`,13:`uint64`},t=`float32`,n=globalThis.Float16Array;function r(){return n!==void 0}function i(n){return n===void 0?t:e[n]??`float32`}function a(e,t){if(t!==`float16`)return e;if(n===void 0)throw RangeError(`This model declares a float16 input, but this environment has no Float16Array.`);return new n(e)}function o(e){return e instanceof Float32Array?e:Float32Array.from(e)}exports.DEFAULT_TENSOR_TYPE=t,exports.asFloat32Array=o,exports.hasFloat16Array=r,exports.tensorTypeFor=i,exports.toFeedData=a;
|
|
2
|
+
//# sourceMappingURL=dtypes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dtypes.cjs","names":[],"sources":["../../../src/vision/core/dtypes.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * What element type the graph declares, and what typed array that means.\n *\n * A model exported with `half=True` declares its input as `float16`, and ONNX\n * Runtime refuses a float32 feed against it:\n *\n * ```text\n * Unexpected input data type. Actual: (tensor(float)) , expected: (tensor(float16))\n * ```\n *\n * The session opens without complaint, so the failure lands on the caller's\n * first `predict()` rather than on `create()`. Preprocessing stays in\n * `Float32Array` because that is where the normalization arithmetic belongs —\n * `(value / 255 - mean) / std` in half precision loses exactly the small\n * differences normalization exists to preserve — and the conversion happens\n * once, at the feed boundary.\n *\n * Outputs travel the other way. Float16 carries 11 bits of mantissa, so at\n * pixel scale its neighbouring values are 0.5 apart around coordinate 640 and\n * 1.0 apart around 1280: decoding boxes in that type quantises every coordinate\n * before NMS and the scale-back to original-image pixels ever run. {@link\n * asFloat32Array} widens a half-precision output before the decoders touch it.\n *\n * `Float16Array` is what ORT requires for a half tensor — a `Uint16Array` of\n * the same bits is rejected — and it is not in every browser yet, so\n * {@link hasFloat16Array} exists to refuse at session creation instead of on\n * the first frame.\n */\n\n/** ONNX `TensorProto.DataType` values this SDK can name. */\nconst ELEM_TYPE_TO_TENSOR_TYPE: Readonly<Record<number, string>> = {\n 1: \"float32\",\n 2: \"uint8\",\n 3: \"int8\",\n 4: \"uint16\",\n 5: \"int16\",\n 6: \"int32\",\n 7: \"int64\",\n 9: \"bool\",\n 10: \"float16\",\n 11: \"float64\",\n 12: \"uint32\",\n 13: \"uint64\",\n};\n\n/** The element type every task preprocesses to, and the fallback when unknown. */\nexport const DEFAULT_TENSOR_TYPE = \"float32\";\n\n/**\n * The structural surface of `Float16Array` this module needs.\n *\n * Declared here rather than pulled from `lib.es2025`: the package targets\n * ES2022 so it builds for consumers on older TypeScript, and the only thing\n * needed is \"an array-like of numbers ORT recognises as half precision\".\n */\nexport interface Float16ArrayLike {\n readonly length: number;\n readonly buffer: ArrayBufferLike;\n [index: number]: number;\n}\n\ninterface Float16ArrayConstructor {\n new (input: ArrayLike<number> | number): Float16ArrayLike;\n}\n\nconst FLOAT16_ARRAY = (globalThis as { Float16Array?: Float16ArrayConstructor }).Float16Array;\n\n/**\n * Whether this environment can build a half-precision tensor at all.\n *\n * @returns `true` when the runtime exposes `Float16Array`. ORT rejects any\n * other typed array for a `float16` tensor, so a `false` here means half\n * precision is unreachable in this browser no matter what the model declares.\n */\nexport function hasFloat16Array(): boolean {\n return FLOAT16_ARRAY !== undefined;\n}\n\n/**\n * Name the tensor type an ONNX element type maps to.\n *\n * @param elemType A `TensorProto.DataType` value read off the graph.\n * @returns The ORT tensor type name, or `\"float32\"` for anything unrecognised —\n * which is what every task produces anyway, so an unknown type surfaces as\n * ORT's own type error against the real graph rather than as a lookup miss.\n */\nexport function tensorTypeFor(elemType: number | undefined): string {\n if (elemType === undefined) return DEFAULT_TENSOR_TYPE;\n return ELEM_TYPE_TO_TENSOR_TYPE[elemType] ?? DEFAULT_TENSOR_TYPE;\n}\n\n/**\n * Convert a preprocessed float32 buffer to the type a feed must carry.\n *\n * @param data The preprocessed planar buffer.\n * @param tensorType The tensor type the input declares, from\n * {@link tensorTypeFor}.\n * @returns `data` itself for a float32 input, or a `Float16Array` holding the\n * same values for a half-precision one.\n * @throws {RangeError} If half precision is asked for and the runtime has no\n * `Float16Array`. Callers convert this into a `ModelLoadError` at session\n * creation, so it never reaches a `predict()`.\n */\nexport function toFeedData(\n data: Float32Array,\n tensorType: string,\n): Float32Array | Float16ArrayLike {\n if (tensorType !== \"float16\") return data;\n if (FLOAT16_ARRAY === undefined) {\n throw new RangeError(\n \"This model declares a float16 input, but this environment has no Float16Array.\",\n );\n }\n return new FLOAT16_ARRAY(data);\n}\n\n/**\n * Widen a model output to `Float32Array`, without copying when it already is.\n *\n * @param data A tensor's `data`, as ORT returned it.\n * @returns The same values as a `Float32Array`. A float32 output is passed\n * through untouched; anything else is copied once.\n */\nexport function asFloat32Array(data: unknown): Float32Array {\n if (data instanceof Float32Array) return data;\n return Float32Array.from(data as ArrayLike<number>);\n}\n"],"mappings":"AA+BA,IAAM,EAA6D,CAC/D,EAAG,UACH,EAAG,QACH,EAAG,OACH,EAAG,SACH,EAAG,QACH,EAAG,QACH,EAAG,QACH,EAAG,OACH,GAAI,UACJ,GAAI,UACJ,GAAI,SACJ,GAAI,QACR,EAGa,EAAsB,UAmB7B,EAAiB,WAA0D,aASjF,SAAgB,GAA2B,CACvC,OAAO,IAAkB,IAAA,EAC7B,CAUA,SAAgB,EAAc,EAAsC,CAEhE,OADI,IAAa,IAAA,GAAkB,EAC5B,EAAyB,IAAA,SACpC,CAcA,SAAgB,EACZ,EACA,EAC+B,CAC/B,GAAI,IAAe,UAAW,OAAO,EACrC,GAAI,IAAkB,IAAA,GAClB,MAAU,WACN,gFACJ,EAEJ,OAAO,IAAI,EAAc,CAAI,CACjC,CASA,SAAgB,EAAe,EAA6B,CAExD,OADI,aAAgB,aAAqB,EAClC,aAAa,KAAK,CAAyB,CACtD"}
|