tempest-react-sdk 0.7.0 → 0.8.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 +41 -28
- package/bin/create-tempest-app.mjs +141 -70
- package/bin/tempest.mjs +282 -0
- package/dist/sw.cjs +2 -0
- package/dist/sw.cjs.map +1 -0
- package/dist/sw.d.ts +103 -0
- package/dist/sw.js +95 -0
- package/dist/sw.js.map +1 -0
- package/dist/tempest-react-sdk.cjs +3 -3
- package/dist/tempest-react-sdk.cjs.map +1 -1
- package/dist/tempest-react-sdk.js +1541 -1626
- package/dist/tempest-react-sdk.js.map +1 -1
- package/package.json +10 -2
- package/template/_prettierrc.json +9 -0
- package/template/eslint.config.js +18 -1
- package/template/package.json +7 -1
- package/template-pwa/README.md +64 -0
- package/template-pwa/_env.example +7 -0
- package/template-pwa/index.html +22 -0
- package/template-pwa/package.json +6 -0
- package/template-pwa/public/icon-maskable.svg +4 -0
- package/template-pwa/public/icon.svg +4 -0
- package/template-pwa/public/manifest.webmanifest +35 -0
- package/template-pwa/src/main.tsx +36 -0
- package/template-pwa/src/pages/Dashboard.tsx +73 -0
- package/template-pwa/src/sw.ts +35 -0
- package/template-pwa/src/vite-env.d.ts +12 -0
- package/template-pwa/vite.sw.config.ts +27 -0
package/bin/tempest.mjs
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tempest — project CLI shipped inside tempest-react-sdk.
|
|
3
|
+
//
|
|
4
|
+
// tempest doctor health-check the current project (à la flutter doctor)
|
|
5
|
+
// tempest lint [paths…] run ESLint (report only)
|
|
6
|
+
// tempest fix [paths…] ESLint --fix (sort imports, drop unused, tidy whitespace) + Prettier
|
|
7
|
+
// tempest format [paths…] Prettier --write
|
|
8
|
+
// tempest --help | --version
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const ROOT = process.cwd();
|
|
15
|
+
const SELF_DIR = resolve(fileURLToPath(import.meta.url), "..");
|
|
16
|
+
|
|
17
|
+
const c = {
|
|
18
|
+
reset: "\x1b[0m",
|
|
19
|
+
bold: "\x1b[1m",
|
|
20
|
+
dim: "\x1b[2m",
|
|
21
|
+
green: "\x1b[32m",
|
|
22
|
+
yellow: "\x1b[33m",
|
|
23
|
+
red: "\x1b[31m",
|
|
24
|
+
cyan: "\x1b[36m",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function selfVersion() {
|
|
28
|
+
try {
|
|
29
|
+
return (
|
|
30
|
+
JSON.parse(readFileSync(join(SELF_DIR, "..", "package.json"), "utf8")).version ?? "?"
|
|
31
|
+
);
|
|
32
|
+
} catch {
|
|
33
|
+
return "?";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Resolve a project-local CLI binary (e.g. eslint, prettier). */
|
|
38
|
+
function localBin(name) {
|
|
39
|
+
const p = join(ROOT, "node_modules", ".bin", name);
|
|
40
|
+
return existsSync(p) ? p : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function run(bin, args) {
|
|
44
|
+
const res = spawnSync(bin, args, { stdio: "inherit", cwd: ROOT });
|
|
45
|
+
return res.status ?? 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readJSON(path) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------- doctor ----
|
|
57
|
+
|
|
58
|
+
function fmt(status, label, detail) {
|
|
59
|
+
const mark =
|
|
60
|
+
status === "ok"
|
|
61
|
+
? `${c.green}✓${c.reset}`
|
|
62
|
+
: status === "warn"
|
|
63
|
+
? `${c.yellow}!${c.reset}`
|
|
64
|
+
: `${c.red}✗${c.reset}`;
|
|
65
|
+
const tail = detail ? ` ${c.dim}— ${detail}${c.reset}` : "";
|
|
66
|
+
return ` [${mark}] ${label}${tail}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fileIncludes(path, needle) {
|
|
70
|
+
try {
|
|
71
|
+
return readFileSync(path, "utf8").includes(needle);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function firstExisting(paths) {
|
|
78
|
+
return paths.find((p) => existsSync(join(ROOT, p))) ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function doctor() {
|
|
82
|
+
const checks = [];
|
|
83
|
+
const pkg = readJSON(join(ROOT, "package.json"));
|
|
84
|
+
|
|
85
|
+
// Node
|
|
86
|
+
const [maj, min] = process.versions.node.split(".").map(Number);
|
|
87
|
+
const nodeOk = maj > 20 || (maj === 20 && min >= 19);
|
|
88
|
+
checks.push([
|
|
89
|
+
nodeOk ? "ok" : "fail",
|
|
90
|
+
`Node ${process.versions.node}`,
|
|
91
|
+
nodeOk ? "" : "requires >= 20.19",
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// package.json
|
|
95
|
+
if (!pkg) {
|
|
96
|
+
checks.push(["fail", "package.json", "not found — run inside your project root"]);
|
|
97
|
+
return report(checks);
|
|
98
|
+
}
|
|
99
|
+
checks.push(["ok", "package.json found"]);
|
|
100
|
+
|
|
101
|
+
// SDK dependency + installed
|
|
102
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
|
|
103
|
+
checks.push(
|
|
104
|
+
deps["tempest-react-sdk"]
|
|
105
|
+
? ["ok", "tempest-react-sdk in dependencies", deps["tempest-react-sdk"]]
|
|
106
|
+
: [
|
|
107
|
+
"fail",
|
|
108
|
+
"tempest-react-sdk in dependencies",
|
|
109
|
+
"add it: npm install tempest-react-sdk",
|
|
110
|
+
],
|
|
111
|
+
);
|
|
112
|
+
checks.push(
|
|
113
|
+
existsSync(join(ROOT, "node_modules", "tempest-react-sdk"))
|
|
114
|
+
? ["ok", "tempest-react-sdk installed"]
|
|
115
|
+
: ["fail", "tempest-react-sdk installed", "run npm install"],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// React peers
|
|
119
|
+
const hasReact = deps.react && deps["react-dom"];
|
|
120
|
+
checks.push(
|
|
121
|
+
hasReact
|
|
122
|
+
? ["ok", "react + react-dom present"]
|
|
123
|
+
: ["fail", "react + react-dom present", "install react react-dom"],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Vite config + createViteConfig
|
|
127
|
+
const viteCfg = firstExisting(["vite.config.ts", "vite.config.js", "vite.config.mjs"]);
|
|
128
|
+
if (!viteCfg) {
|
|
129
|
+
checks.push(["warn", "vite config", "no vite.config.* found"]);
|
|
130
|
+
} else {
|
|
131
|
+
checks.push(
|
|
132
|
+
fileIncludes(join(ROOT, viteCfg), "createViteConfig")
|
|
133
|
+
? ["ok", `${viteCfg} uses createViteConfig`]
|
|
134
|
+
: ["warn", `${viteCfg}`, "not using createViteConfig from tempest-react-sdk/vite"],
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// tsconfig @ alias
|
|
139
|
+
const tsc = readJSON(join(ROOT, "tsconfig.json"));
|
|
140
|
+
const paths = tsc?.compilerOptions?.paths ?? {};
|
|
141
|
+
checks.push(
|
|
142
|
+
paths["@/*"]
|
|
143
|
+
? ["ok", 'tsconfig "@/*" alias']
|
|
144
|
+
: ["warn", 'tsconfig "@/*" alias', 'add "paths": { "@/*": ["./src/*"] }'],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// styles.css imported at entry
|
|
148
|
+
const entry = firstExisting(["src/main.tsx", "src/main.ts", "src/index.tsx", "src/index.ts"]);
|
|
149
|
+
if (entry) {
|
|
150
|
+
checks.push(
|
|
151
|
+
fileIncludes(join(ROOT, entry), "tempest-react-sdk/styles.css")
|
|
152
|
+
? ["ok", `${entry} imports styles.css`]
|
|
153
|
+
: ["warn", `${entry}`, 'add import "tempest-react-sdk/styles.css"'],
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
checks.push(["warn", "app entry", "no src/main.tsx found"]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// tooling
|
|
160
|
+
checks.push(
|
|
161
|
+
firstExisting(["eslint.config.js", "eslint.config.mjs", ".eslintrc.cjs", ".eslintrc.json"])
|
|
162
|
+
? ["ok", "ESLint config present"]
|
|
163
|
+
: ["warn", "ESLint config", "no eslint config — `tempest fix` needs it"],
|
|
164
|
+
);
|
|
165
|
+
checks.push(
|
|
166
|
+
localBin("eslint")
|
|
167
|
+
? ["ok", "eslint installed"]
|
|
168
|
+
: ["warn", "eslint installed", "npm i -D eslint"],
|
|
169
|
+
);
|
|
170
|
+
checks.push(
|
|
171
|
+
localBin("prettier")
|
|
172
|
+
? ["ok", "prettier installed"]
|
|
173
|
+
: ["warn", "prettier installed", "npm i -D prettier"],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// .env
|
|
177
|
+
if (existsSync(join(ROOT, ".env"))) checks.push(["ok", ".env present"]);
|
|
178
|
+
else if (existsSync(join(ROOT, ".env.example")))
|
|
179
|
+
checks.push(["warn", ".env", "only .env.example — copy it: cp .env.example .env"]);
|
|
180
|
+
|
|
181
|
+
return report(checks);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function report(checks) {
|
|
185
|
+
console.log(`\n${c.bold}${c.cyan}tempest doctor${c.reset} ${c.dim}(${ROOT})${c.reset}\n`);
|
|
186
|
+
for (const [status, label, detail] of checks) console.log(fmt(status, label, detail));
|
|
187
|
+
const fails = checks.filter((x) => x[0] === "fail").length;
|
|
188
|
+
const warns = checks.filter((x) => x[0] === "warn").length;
|
|
189
|
+
console.log("");
|
|
190
|
+
if (fails)
|
|
191
|
+
console.log(
|
|
192
|
+
`${c.red}✗ ${fails} problem(s)${c.reset}${warns ? `, ${c.yellow}${warns} warning(s)${c.reset}` : ""}.`,
|
|
193
|
+
);
|
|
194
|
+
else if (warns)
|
|
195
|
+
console.log(`${c.yellow}! ${warns} warning(s)${c.reset} — usable, but worth fixing.`);
|
|
196
|
+
else console.log(`${c.green}✓ No issues found.${c.reset}`);
|
|
197
|
+
console.log("");
|
|
198
|
+
return fails ? 1 : 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ------------------------------------------------------- lint / fix / fmt ----
|
|
202
|
+
|
|
203
|
+
function requireBin(name) {
|
|
204
|
+
const bin = localBin(name);
|
|
205
|
+
if (!bin) {
|
|
206
|
+
console.error(
|
|
207
|
+
`${c.red}✗ ${name} not found in node_modules.${c.reset} Install it: ${c.bold}npm i -D ${name}${c.reset}`,
|
|
208
|
+
);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
return bin;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function lint(paths) {
|
|
215
|
+
return run(requireBin("eslint"), paths.length ? paths : ["."]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function fix(paths) {
|
|
219
|
+
const targets = paths.length ? paths : ["."];
|
|
220
|
+
console.log(`${c.dim}→ eslint --fix (sort imports · drop unused · tidy whitespace)${c.reset}`);
|
|
221
|
+
const eslintStatus = run(requireBin("eslint"), [...targets, "--fix"]);
|
|
222
|
+
const prettier = localBin("prettier");
|
|
223
|
+
let prettierStatus = 0;
|
|
224
|
+
if (prettier) {
|
|
225
|
+
console.log(`${c.dim}→ prettier --write${c.reset}`);
|
|
226
|
+
prettierStatus = run(prettier, ["--write", ...targets]);
|
|
227
|
+
} else {
|
|
228
|
+
console.log(`${c.yellow}! prettier not installed — skipping format pass${c.reset}`);
|
|
229
|
+
}
|
|
230
|
+
return eslintStatus || prettierStatus;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function format(paths) {
|
|
234
|
+
return run(requireBin("prettier"), ["--write", ...(paths.length ? paths : ["."])]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ------------------------------------------------------------------ main ----
|
|
238
|
+
|
|
239
|
+
function usage() {
|
|
240
|
+
console.log(`
|
|
241
|
+
${c.bold}${c.cyan}tempest${c.reset} ${c.dim}v${selfVersion()}${c.reset} — project CLI for tempest-react-sdk apps
|
|
242
|
+
|
|
243
|
+
${c.bold}Usage${c.reset}
|
|
244
|
+
tempest <command> [paths…]
|
|
245
|
+
|
|
246
|
+
${c.bold}Commands${c.reset}
|
|
247
|
+
${c.bold}doctor${c.reset} Health-check the current project
|
|
248
|
+
${c.bold}lint${c.reset} [paths] Run ESLint (report only)
|
|
249
|
+
${c.bold}fix${c.reset} [paths] ESLint --fix (sort imports, remove unused, tidy whitespace) + Prettier
|
|
250
|
+
${c.bold}format${c.reset} [paths] Prettier --write
|
|
251
|
+
|
|
252
|
+
${c.bold}Options${c.reset}
|
|
253
|
+
-h, --help Show this help
|
|
254
|
+
-v, --version Show version
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
259
|
+
|
|
260
|
+
if (cmd === "-v" || cmd === "--version") {
|
|
261
|
+
console.log(selfVersion());
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
265
|
+
usage();
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const commands = {
|
|
270
|
+
doctor: () => doctor(),
|
|
271
|
+
lint: () => lint(rest),
|
|
272
|
+
fix: () => fix(rest),
|
|
273
|
+
format: () => format(rest),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (!commands[cmd]) {
|
|
277
|
+
console.error(`${c.red}✗ Unknown command: ${cmd}${c.reset}`);
|
|
278
|
+
usage();
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
process.exit(commands[cmd]());
|
package/dist/sw.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});async function d(i){if(typeof navigator>"u"||!("serviceWorker"in navigator))return null;try{const t=await navigator.serviceWorker.register(i.url,{scope:i.scope});return t.active&&i.onReady?.(t),t.addEventListener("updatefound",()=>{const n=t.installing;n&&n.addEventListener("statechange",()=>{n.state==="installed"&&navigator.serviceWorker.controller&&i.onUpdate?.(n,t)})}),t}catch(t){return i.onError?.(t),null}}function g(i){i.postMessage({type:"SKIP_WAITING"})}async function v(){if(typeof navigator>"u"||!("serviceWorker"in navigator))return 0;const i=await navigator.serviceWorker.getRegistrations();let t=0;for(const n of i)await n.unregister()&&(t+=1);return t}function c(){return globalThis}function y(i={}){const t=c(),{defaultTitle:n="Notificação",defaultIcon:e,defaultBadge:o,transform:s}=i;t.addEventListener("push",a=>{if(!a.data)return;let l;try{l=a.data.json()}catch{l={title:n,body:a.data.text()}}const r=s?s(l):l;if(!r)return;const u=r.title??n,f={body:r.body,icon:r.icon??e,badge:r.badge??o,image:r.image,tag:r.tag,data:{url:r.url??"/",...r.data??{}}};a.waitUntil(t.registration.showNotification(u,f))})}function p(i={}){const t=c(),n=i.resolveUrl??(e=>{if(typeof e=="string")return e;if(e&&typeof e=="object"&&"url"in e){const o=e.url;return typeof o=="string"?o:"/"}return"/"});t.addEventListener("notificationclick",e=>{e.notification.close();const o=n(e.notification.data);e.waitUntil((async()=>{const s=await t.clients.matchAll({type:"window",includeUncontrolled:!0});for(const a of s)if(a.url.includes(o))return a.focus();return t.clients.openWindow(o)})())})}function k(){const i=c();i.addEventListener("message",t=>{t.data?.type==="SKIP_WAITING"&&i.skipWaiting()})}exports.installNotificationClickHandler=p;exports.installPushHandler=y;exports.installSkipWaitingListener=k;exports.registerServiceWorker=d;exports.skipWaiting=g;exports.unregisterAllServiceWorkers=v;
|
|
2
|
+
//# sourceMappingURL=sw.cjs.map
|
package/dist/sw.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sw.cjs","sources":["../src/sw/register-service-worker.ts","../src/sw/create-push-handler.ts"],"sourcesContent":["export interface RegisterServiceWorkerOptions {\n /** Public URL of the compiled service worker file (e.g. `/sw.js`). */\n url: string;\n /** SW scope (default: SW directory). */\n scope?: string;\n /** Called once the registration is active. */\n onReady?: (registration: ServiceWorkerRegistration) => void;\n /**\n * Called when a new worker has finished installing while another worker\n * still controls the page. The host app typically prompts the user to\n * reload and then calls {@link skipWaiting} on the returned worker.\n */\n onUpdate?: (waiting: ServiceWorker, registration: ServiceWorkerRegistration) => void;\n /** Called on registration failure. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * Register a service worker with consistent update-detection wiring.\n *\n * Skips silently when the runtime has no `serviceWorker` support. The host\n * app keeps full control over the SW file — this helper only handles the\n * boilerplate around `register()` and `updatefound`.\n *\n * @returns The registration when it succeeds, or `null` when unsupported.\n */\nexport async function registerServiceWorker(\n options: RegisterServiceWorkerOptions,\n): Promise<ServiceWorkerRegistration | null> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return null;\n }\n\n try {\n const registration = await navigator.serviceWorker.register(options.url, {\n scope: options.scope,\n });\n\n if (registration.active) options.onReady?.(registration);\n\n registration.addEventListener(\"updatefound\", () => {\n const installing = registration.installing;\n if (!installing) return;\n installing.addEventListener(\"statechange\", () => {\n if (installing.state === \"installed\" && navigator.serviceWorker.controller) {\n options.onUpdate?.(installing, registration);\n }\n });\n });\n\n return registration;\n } catch (error) {\n options.onError?.(error);\n return null;\n }\n}\n\n/**\n * Tell a waiting worker to activate immediately. Pair with `onUpdate` to roll\n * out updates after the user confirms a reload prompt.\n */\nexport function skipWaiting(worker: ServiceWorker): void {\n worker.postMessage({ type: \"SKIP_WAITING\" });\n}\n\n/**\n * Unregister all registered service workers for this origin.\n *\n * @returns Number of workers that were unregistered.\n */\nexport async function unregisterAllServiceWorkers(): Promise<number> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) return 0;\n const registrations = await navigator.serviceWorker.getRegistrations();\n let count = 0;\n for (const registration of registrations) {\n const result = await registration.unregister();\n if (result) count += 1;\n }\n return count;\n}\n","/**\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"],"names":["registerServiceWorker","options","registration","installing","error","skipWaiting","worker","unregisterAllServiceWorkers","registrations","count","getSwScope","installPushHandler","sw","defaultTitle","defaultIcon","defaultBadge","transform","event","raw","payload","title","notification","installNotificationClickHandler","resolveUrl","data","url","target","clients","client","installSkipWaitingListener"],"mappings":"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"}
|
package/dist/sw.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install a `notificationclick` handler that focuses an existing client when
|
|
3
|
+
* possible and falls back to opening a new window.
|
|
4
|
+
*/
|
|
5
|
+
export declare function installNotificationClickHandler(options?: InstallNotificationClickHandlerOptions): void;
|
|
6
|
+
|
|
7
|
+
export declare interface InstallNotificationClickHandlerOptions {
|
|
8
|
+
/** Resolve the destination URL from the notification data. Default: `data.url`. */
|
|
9
|
+
resolveUrl?: (data: unknown) => string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Install a `push` event listener that parses the payload as JSON (with a
|
|
14
|
+
* plain-text fallback) and shows a notification.
|
|
15
|
+
*/
|
|
16
|
+
export declare function installPushHandler(options?: InstallPushHandlerOptions): void;
|
|
17
|
+
|
|
18
|
+
export declare interface InstallPushHandlerOptions {
|
|
19
|
+
/** Title used when the payload omits one. */
|
|
20
|
+
defaultTitle?: string;
|
|
21
|
+
/** Icon used when the payload omits one. */
|
|
22
|
+
defaultIcon?: string;
|
|
23
|
+
/** Badge image (mobile). */
|
|
24
|
+
defaultBadge?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Transform the raw payload before showing the notification. Return `null`
|
|
27
|
+
* to suppress the notification entirely (e.g. silent pings).
|
|
28
|
+
*/
|
|
29
|
+
transform?: (payload: PushPayload) => PushPayload | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Install a `message` listener that activates a waiting worker when the host
|
|
34
|
+
* app sends `{ type: "SKIP_WAITING" }`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function installSkipWaitingListener(): void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Service-worker context helpers for handling `push` and `notificationclick`
|
|
40
|
+
* events. Import these inside your own `sw.ts` — they expect to run in the
|
|
41
|
+
* service-worker global scope, not in the main thread.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* /// <reference lib="webworker" />
|
|
45
|
+
* import { installPushHandler, installNotificationClickHandler } from "tempest-react-sdk";
|
|
46
|
+
*
|
|
47
|
+
* installPushHandler({ defaultIcon: "/icons/Logo.png" });
|
|
48
|
+
* installNotificationClickHandler();
|
|
49
|
+
*/
|
|
50
|
+
export declare interface PushPayload {
|
|
51
|
+
title?: string;
|
|
52
|
+
body?: string;
|
|
53
|
+
icon?: string;
|
|
54
|
+
badge?: string;
|
|
55
|
+
image?: string;
|
|
56
|
+
tag?: string;
|
|
57
|
+
url?: string;
|
|
58
|
+
/** Arbitrary extra data forwarded to `event.notification.data`. */
|
|
59
|
+
data?: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Register a service worker with consistent update-detection wiring.
|
|
64
|
+
*
|
|
65
|
+
* Skips silently when the runtime has no `serviceWorker` support. The host
|
|
66
|
+
* app keeps full control over the SW file — this helper only handles the
|
|
67
|
+
* boilerplate around `register()` and `updatefound`.
|
|
68
|
+
*
|
|
69
|
+
* @returns The registration when it succeeds, or `null` when unsupported.
|
|
70
|
+
*/
|
|
71
|
+
export declare function registerServiceWorker(options: RegisterServiceWorkerOptions): Promise<ServiceWorkerRegistration | null>;
|
|
72
|
+
|
|
73
|
+
export declare interface RegisterServiceWorkerOptions {
|
|
74
|
+
/** Public URL of the compiled service worker file (e.g. `/sw.js`). */
|
|
75
|
+
url: string;
|
|
76
|
+
/** SW scope (default: SW directory). */
|
|
77
|
+
scope?: string;
|
|
78
|
+
/** Called once the registration is active. */
|
|
79
|
+
onReady?: (registration: ServiceWorkerRegistration) => void;
|
|
80
|
+
/**
|
|
81
|
+
* Called when a new worker has finished installing while another worker
|
|
82
|
+
* still controls the page. The host app typically prompts the user to
|
|
83
|
+
* reload and then calls {@link skipWaiting} on the returned worker.
|
|
84
|
+
*/
|
|
85
|
+
onUpdate?: (waiting: ServiceWorker, registration: ServiceWorkerRegistration) => void;
|
|
86
|
+
/** Called on registration failure. */
|
|
87
|
+
onError?: (error: unknown) => void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tell a waiting worker to activate immediately. Pair with `onUpdate` to roll
|
|
92
|
+
* out updates after the user confirms a reload prompt.
|
|
93
|
+
*/
|
|
94
|
+
export declare function skipWaiting(worker: ServiceWorker): void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Unregister all registered service workers for this origin.
|
|
98
|
+
*
|
|
99
|
+
* @returns Number of workers that were unregistered.
|
|
100
|
+
*/
|
|
101
|
+
export declare function unregisterAllServiceWorkers(): Promise<number>;
|
|
102
|
+
|
|
103
|
+
export { }
|
package/dist/sw.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
async function d(n) {
|
|
2
|
+
if (typeof navigator > "u" || !("serviceWorker" in navigator))
|
|
3
|
+
return null;
|
|
4
|
+
try {
|
|
5
|
+
const t = await navigator.serviceWorker.register(n.url, {
|
|
6
|
+
scope: n.scope
|
|
7
|
+
});
|
|
8
|
+
return t.active && n.onReady?.(t), t.addEventListener("updatefound", () => {
|
|
9
|
+
const i = t.installing;
|
|
10
|
+
i && i.addEventListener("statechange", () => {
|
|
11
|
+
i.state === "installed" && navigator.serviceWorker.controller && n.onUpdate?.(i, t);
|
|
12
|
+
});
|
|
13
|
+
}), t;
|
|
14
|
+
} catch (t) {
|
|
15
|
+
return n.onError?.(t), null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function g(n) {
|
|
19
|
+
n.postMessage({ type: "SKIP_WAITING" });
|
|
20
|
+
}
|
|
21
|
+
async function v() {
|
|
22
|
+
if (typeof navigator > "u" || !("serviceWorker" in navigator)) return 0;
|
|
23
|
+
const n = await navigator.serviceWorker.getRegistrations();
|
|
24
|
+
let t = 0;
|
|
25
|
+
for (const i of n)
|
|
26
|
+
await i.unregister() && (t += 1);
|
|
27
|
+
return t;
|
|
28
|
+
}
|
|
29
|
+
function l() {
|
|
30
|
+
return globalThis;
|
|
31
|
+
}
|
|
32
|
+
function y(n = {}) {
|
|
33
|
+
const t = l(), { defaultTitle: i = "Notificação", defaultIcon: e, defaultBadge: o, transform: s } = n;
|
|
34
|
+
t.addEventListener("push", (a) => {
|
|
35
|
+
if (!a.data) return;
|
|
36
|
+
let c;
|
|
37
|
+
try {
|
|
38
|
+
c = a.data.json();
|
|
39
|
+
} catch {
|
|
40
|
+
c = { title: i, body: a.data.text() };
|
|
41
|
+
}
|
|
42
|
+
const r = s ? s(c) : c;
|
|
43
|
+
if (!r) return;
|
|
44
|
+
const u = r.title ?? i, f = {
|
|
45
|
+
body: r.body,
|
|
46
|
+
icon: r.icon ?? e,
|
|
47
|
+
badge: r.badge ?? o,
|
|
48
|
+
image: r.image,
|
|
49
|
+
tag: r.tag,
|
|
50
|
+
data: { url: r.url ?? "/", ...r.data ?? {} }
|
|
51
|
+
};
|
|
52
|
+
a.waitUntil(t.registration.showNotification(u, f));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function p(n = {}) {
|
|
56
|
+
const t = l(), i = n.resolveUrl ?? ((e) => {
|
|
57
|
+
if (typeof e == "string") return e;
|
|
58
|
+
if (e && typeof e == "object" && "url" in e) {
|
|
59
|
+
const o = e.url;
|
|
60
|
+
return typeof o == "string" ? o : "/";
|
|
61
|
+
}
|
|
62
|
+
return "/";
|
|
63
|
+
});
|
|
64
|
+
t.addEventListener("notificationclick", (e) => {
|
|
65
|
+
e.notification.close();
|
|
66
|
+
const o = i(e.notification.data);
|
|
67
|
+
e.waitUntil(
|
|
68
|
+
(async () => {
|
|
69
|
+
const s = await t.clients.matchAll({
|
|
70
|
+
type: "window",
|
|
71
|
+
includeUncontrolled: !0
|
|
72
|
+
});
|
|
73
|
+
for (const a of s)
|
|
74
|
+
if (a.url.includes(o))
|
|
75
|
+
return a.focus();
|
|
76
|
+
return t.clients.openWindow(o);
|
|
77
|
+
})()
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function w() {
|
|
82
|
+
const n = l();
|
|
83
|
+
n.addEventListener("message", (t) => {
|
|
84
|
+
t.data?.type === "SKIP_WAITING" && n.skipWaiting();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
p as installNotificationClickHandler,
|
|
89
|
+
y as installPushHandler,
|
|
90
|
+
w as installSkipWaitingListener,
|
|
91
|
+
d as registerServiceWorker,
|
|
92
|
+
g as skipWaiting,
|
|
93
|
+
v as unregisterAllServiceWorkers
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=sw.js.map
|
package/dist/sw.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sw.js","sources":["../src/sw/register-service-worker.ts","../src/sw/create-push-handler.ts"],"sourcesContent":["export interface RegisterServiceWorkerOptions {\n /** Public URL of the compiled service worker file (e.g. `/sw.js`). */\n url: string;\n /** SW scope (default: SW directory). */\n scope?: string;\n /** Called once the registration is active. */\n onReady?: (registration: ServiceWorkerRegistration) => void;\n /**\n * Called when a new worker has finished installing while another worker\n * still controls the page. The host app typically prompts the user to\n * reload and then calls {@link skipWaiting} on the returned worker.\n */\n onUpdate?: (waiting: ServiceWorker, registration: ServiceWorkerRegistration) => void;\n /** Called on registration failure. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * Register a service worker with consistent update-detection wiring.\n *\n * Skips silently when the runtime has no `serviceWorker` support. The host\n * app keeps full control over the SW file — this helper only handles the\n * boilerplate around `register()` and `updatefound`.\n *\n * @returns The registration when it succeeds, or `null` when unsupported.\n */\nexport async function registerServiceWorker(\n options: RegisterServiceWorkerOptions,\n): Promise<ServiceWorkerRegistration | null> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return null;\n }\n\n try {\n const registration = await navigator.serviceWorker.register(options.url, {\n scope: options.scope,\n });\n\n if (registration.active) options.onReady?.(registration);\n\n registration.addEventListener(\"updatefound\", () => {\n const installing = registration.installing;\n if (!installing) return;\n installing.addEventListener(\"statechange\", () => {\n if (installing.state === \"installed\" && navigator.serviceWorker.controller) {\n options.onUpdate?.(installing, registration);\n }\n });\n });\n\n return registration;\n } catch (error) {\n options.onError?.(error);\n return null;\n }\n}\n\n/**\n * Tell a waiting worker to activate immediately. Pair with `onUpdate` to roll\n * out updates after the user confirms a reload prompt.\n */\nexport function skipWaiting(worker: ServiceWorker): void {\n worker.postMessage({ type: \"SKIP_WAITING\" });\n}\n\n/**\n * Unregister all registered service workers for this origin.\n *\n * @returns Number of workers that were unregistered.\n */\nexport async function unregisterAllServiceWorkers(): Promise<number> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) return 0;\n const registrations = await navigator.serviceWorker.getRegistrations();\n let count = 0;\n for (const registration of registrations) {\n const result = await registration.unregister();\n if (result) count += 1;\n }\n return count;\n}\n","/**\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"],"names":["registerServiceWorker","options","registration","installing","error","skipWaiting","worker","unregisterAllServiceWorkers","registrations","count","getSwScope","installPushHandler","sw","defaultTitle","defaultIcon","defaultBadge","transform","event","raw","payload","title","notification","installNotificationClickHandler","resolveUrl","data","url","target","clients","client","installSkipWaitingListener"],"mappings":"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"}
|