@taskup/web 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TaskUp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @taskup/web
2
+
3
+ Small browser SDK for registered TaskUp controls and opt-in error reporting. It never reads cookies, form values, request bodies, tokens, or environment variables.
4
+
5
+ ## Install
6
+
7
+ After the first public npm release:
8
+
9
+ npm install @taskup/web
10
+
11
+ Until then, install the package file provided on the TaskUp setup page. A local package can be made with:
12
+
13
+ npm pack ./packages/web
14
+
15
+ ## Next.js
16
+
17
+ Copy this into a client component and replace the example keys with controls you registered in TaskUp:
18
+
19
+ "use client";
20
+ import { useTaskUp } from "@taskup/web/react";
21
+
22
+ export function TaskUpExample() {
23
+ const taskup = useTaskUp({
24
+ projectKey: "YOUR_PUBLIC_PROJECT_KEY",
25
+ endpoint: "https://task-up.org",
26
+ });
27
+ return <>
28
+ <h1>{taskup.string("homepage_headline", "Welcome")}</h1>
29
+ <button disabled={!taskup.boolean("registrations_open", true)}>Register</button>
30
+ </>;
31
+ }
32
+
33
+ The hook refreshes controls and reports uncaught browser errors. The fallback stays in use when TaskUp is unavailable. For non-React apps, import createTaskUp from @taskup/web, call refresh(), and read controls with string(), number(), or boolean().
34
+
35
+ For handled errors, call reportError(error) or reportNetworkFailure(path, status). Supply a userId callback only if your app has already authenticated the user. A deployment version can be supplied in the options.
@@ -0,0 +1,21 @@
1
+ export type TaskUpOptions = {
2
+ projectKey: string;
3
+ endpoint: string;
4
+ userId?: () => string | undefined;
5
+ version?: string;
6
+ };
7
+ type Value = string | number | boolean;
8
+ /** A small, opt-in browser client. It never reads cookies, forms, bodies or tokens. */
9
+ export declare function createTaskUp(options: TaskUpOptions): {
10
+ refresh: () => Promise<void>;
11
+ subscribe(listener: () => void): () => boolean;
12
+ getSnapshot: () => number;
13
+ string: (key: string, fallback: string) => string;
14
+ number: (key: string, fallback: number) => number;
15
+ boolean: (key: string, fallback: boolean) => boolean;
16
+ get: <T extends Value>(key: string, fallback: T) => T;
17
+ reportError(error: unknown): void;
18
+ reportNetworkFailure(path: string, status: number): void;
19
+ observeGlobalErrors(): () => void;
20
+ };
21
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /** A small, opt-in browser client. It never reads cookies, forms, bodies or tokens. */
2
+ export function createTaskUp(options) {
3
+ const base = options.endpoint.replace(/\/$/, "");
4
+ const values = new Map();
5
+ const listeners = new Set();
6
+ let connected = false;
7
+ let revision = 0;
8
+ const notify = () => { revision++; listeners.forEach(listener => listener()); };
9
+ const post = (path, payload) => {
10
+ if (typeof window === "undefined")
11
+ return Promise.resolve(undefined);
12
+ return fetch(`${base}${path}`, { method: "POST", mode: "cors", credentials: "omit", headers: { "Content-Type": "text/plain" }, body: JSON.stringify(payload), keepalive: true }).catch(() => undefined);
13
+ };
14
+ async function refresh() {
15
+ if (typeof window === "undefined")
16
+ return;
17
+ try {
18
+ const response = await fetch(`${base}/api/v1/controls?projectKey=${encodeURIComponent(options.projectKey)}`, { mode: "cors", credentials: "omit" });
19
+ if (!response.ok)
20
+ return;
21
+ const result = await response.json();
22
+ for (const [key, value] of Object.entries(result.values ?? {}))
23
+ if (["string", "number", "boolean"].includes(typeof value))
24
+ values.set(key, value);
25
+ notify();
26
+ if (!connected) {
27
+ const signal = await post("/api/v1/hello", { projectKey: options.projectKey });
28
+ connected = signal?.ok === true;
29
+ }
30
+ }
31
+ catch { /* Fallbacks remain active while TaskUp is unavailable. */ }
32
+ }
33
+ function read(key, fallback) { const value = values.get(key); return typeof value === typeof fallback ? value : fallback; }
34
+ function report(kind, message) {
35
+ if (typeof window === "undefined")
36
+ return;
37
+ void post("/api/v1/events", {
38
+ projectKey: options.projectKey, kind,
39
+ message: String(message).replace(/[?#][^\s]+/g, "").slice(0, 2000),
40
+ route: window.location.pathname,
41
+ userId: options.userId?.(), version: options.version,
42
+ });
43
+ }
44
+ return {
45
+ refresh,
46
+ subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
47
+ getSnapshot: () => revision,
48
+ string: (key, fallback) => read(key, fallback),
49
+ number: (key, fallback) => read(key, fallback),
50
+ boolean: (key, fallback) => read(key, fallback),
51
+ get: read,
52
+ reportError(error) { report("runtime", error instanceof Error ? error.message : String(error)); },
53
+ reportNetworkFailure(path, status) { report("network", `Request failed: ${path.split("?")[0]} (HTTP ${status})`); },
54
+ observeGlobalErrors() {
55
+ if (typeof window === "undefined")
56
+ return () => { };
57
+ const onError = (event) => report("runtime", event.message || "Unhandled error");
58
+ const onRejection = (event) => report("runtime", event.reason instanceof Error ? event.reason.message : "Unhandled promise rejection");
59
+ window.addEventListener("error", onError);
60
+ window.addEventListener("unhandledrejection", onRejection);
61
+ return () => { window.removeEventListener("error", onError); window.removeEventListener("unhandledrejection", onRejection); };
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,14 @@
1
+ import { type TaskUpOptions } from "./index";
2
+ /** Reads registered controls and refreshes the component after TaskUp updates them. */
3
+ export declare function useTaskUp(options: TaskUpOptions): {
4
+ refresh: () => Promise<void>;
5
+ subscribe(listener: () => void): () => boolean;
6
+ getSnapshot: () => number;
7
+ string: (key: string, fallback: string) => string;
8
+ number: (key: string, fallback: number) => number;
9
+ boolean: (key: string, fallback: boolean) => boolean;
10
+ get: <T extends string | number | boolean>(key: string, fallback: T) => T;
11
+ reportError(error: unknown): void;
12
+ reportNetworkFailure(path: string, status: number): void;
13
+ observeGlobalErrors(): () => void;
14
+ };
package/dist/react.js ADDED
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { useEffect, useMemo, useSyncExternalStore } from "react";
3
+ import { createTaskUp } from "./index";
4
+ /** Reads registered controls and refreshes the component after TaskUp updates them. */
5
+ export function useTaskUp(options) {
6
+ const { projectKey, endpoint, userId, version } = options;
7
+ const client = useMemo(() => createTaskUp({ projectKey, endpoint, userId, version }), [projectKey, endpoint, userId, version]);
8
+ useSyncExternalStore(client.subscribe, client.getSnapshot, () => 0);
9
+ useEffect(() => {
10
+ void client.refresh();
11
+ return client.observeGlobalErrors();
12
+ }, [client]);
13
+ return client;
14
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@taskup/web",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Small browser SDK for TaskUp controls and error reporting",
6
+ "homepage": "https://task-up.org",
7
+ "license": "MIT",
8
+ "publishConfig": { "access": "public" },
9
+ "files": ["dist", "README.md", "LICENSE"],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./react": {
17
+ "types": "./dist/react.d.ts",
18
+ "import": "./dist/react.js",
19
+ "default": "./dist/react.js"
20
+ }
21
+ },
22
+ "types": "./dist/index.d.ts",
23
+ "sideEffects": false,
24
+ "peerDependencies": { "react": ">=18" },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "prepack": "npm run build"
28
+ },
29
+ "devDependencies": { "@types/react": "^19.0.0", "react": "^19.0.0", "typescript": "^5.0.0" }
30
+ }