@signo/sdk 0.1.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 ADDED
@@ -0,0 +1,92 @@
1
+ # @signo/sdk
2
+
3
+ Send [Signo](https://signo.digitospace.com) notifications from your Node backend.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm i @signo/sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { Signo } from '@signo/sdk';
15
+
16
+ const signo = new Signo();
17
+
18
+ await signo.send('a7bx9k', {
19
+ title: 'New sale 馃帀',
20
+ body: 'Order #1043 路 $89.00',
21
+ });
22
+ ```
23
+
24
+ Or the zero-config default client:
25
+
26
+ ```ts
27
+ import signo from '@signo/sdk';
28
+
29
+ await signo.notify('a7bx9k', { title: 'Deploy finished', body: 'prod 路 a1b2c3' });
30
+ ```
31
+
32
+ Sending is public - a topic's namespace is its address, so no API key is needed. Pass the namespace with or without the `signo.` prefix.
33
+
34
+ ## Rich notifications
35
+
36
+ ```ts
37
+ await signo.send('a7bx9k', {
38
+ title: 'New sale 馃帀',
39
+ body: 'Order #1043 路 $89.00',
40
+ subtitle: 'Acme Store',
41
+ image: 'https://cdn.acme.com/p/1043.jpg',
42
+ badge: 1,
43
+ interruptionLevel: 'time-sensitive',
44
+ payload: { orderId: '1043', total: 89.0 },
45
+ });
46
+ ```
47
+
48
+ Every field is optional except `title`:
49
+
50
+ | Field | Type | Notes |
51
+ | --- | --- | --- |
52
+ | `title` | `string` | Required |
53
+ | `body` | `string` | |
54
+ | `subtitle` | `string` | iOS secondary line |
55
+ | `image` | `string` | Android inline; iOS needs a Notification Service Extension |
56
+ | `badge` | `number` | iOS app-icon count |
57
+ | `sound` | `'default' \| null` | `null` = silent |
58
+ | `priority` | `'default' \| 'normal' \| 'high'` | Defaults to `high` |
59
+ | `interruptionLevel` | `'active' \| 'critical' \| 'passive' \| 'time-sensitive'` | iOS |
60
+ | `ttl` / `expiration` | `number` | Drop stale pushes |
61
+ | `categoryId` | `string` | Interactive actions |
62
+ | `mutableContent` | `boolean` | |
63
+ | `payload` | `object` | Arbitrary JSON delivered to the app |
64
+
65
+ ## Result & errors
66
+
67
+ ```ts
68
+ const { eventId, delivered } = await signo.send('a7bx9k', { title: 'Hi' });
69
+ ```
70
+
71
+ A failed request throws a `SignoError` with a `.status`:
72
+
73
+ ```ts
74
+ import { Signo, SignoError } from '@signo/sdk';
75
+
76
+ try {
77
+ await signo.send('nope', { title: 'Hi' });
78
+ } catch (err) {
79
+ if (err instanceof SignoError) console.error(err.status, err.message);
80
+ }
81
+ ```
82
+
83
+ ## Options
84
+
85
+ ```ts
86
+ new Signo({
87
+ apiUrl: 'https://signo.digitospace.com', // override the API base URL
88
+ fetch: myFetch, // custom fetch (defaults to global fetch)
89
+ });
90
+ ```
91
+
92
+ Requires Node 18+ (global `fetch`).
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Signo server SDK - send notifications to your Signo topics from any Node backend.
3
+ *
4
+ * ```ts
5
+ * import { Signo } from '@signo/sdk';
6
+ * const signo = new Signo();
7
+ * await signo.send('a7bx9k', { title: 'New sale 馃帀', body: 'Order #1043 路 $89.00' });
8
+ * ```
9
+ */
10
+ export type Priority = 'default' | 'normal' | 'high';
11
+ export type InterruptionLevel = 'active' | 'critical' | 'passive' | 'time-sensitive';
12
+ /** A notification to deliver. Mirrors the fields the Signo events API accepts. */
13
+ export interface NotifyOptions {
14
+ /** Title line (required). */
15
+ title: string;
16
+ /** Body text under the title. */
17
+ body?: string;
18
+ /** iOS secondary line, shown under the title. */
19
+ subtitle?: string;
20
+ /** Image URL. Android renders it inline; iOS needs a Notification Service Extension. */
21
+ image?: string;
22
+ /** App-icon badge count (iOS). Use 0 to clear it. */
23
+ badge?: number;
24
+ /** 'default' plays the device sound; null delivers silently. Defaults to 'default'. */
25
+ sound?: 'default' | null;
26
+ /** Delivery priority. Defaults to 'high' server-side. */
27
+ priority?: Priority;
28
+ /** iOS interruption level; 'time-sensitive' can break through Focus / DND. */
29
+ interruptionLevel?: InterruptionLevel;
30
+ /** Seconds the push may be retried for before it's dropped. */
31
+ ttl?: number;
32
+ /** Unix timestamp (seconds) after which the push expires. `ttl` takes precedence. */
33
+ expiration?: number;
34
+ /** Interactive category id (must be registered in the app for actions to appear). */
35
+ categoryId?: string;
36
+ /** Let the app modify the notification before display (required for iOS rich media). */
37
+ mutableContent?: boolean;
38
+ /** Arbitrary JSON delivered to the app alongside the notification. */
39
+ payload?: Record<string, unknown>;
40
+ }
41
+ /** Result of a successful send. */
42
+ export interface SendResult {
43
+ /** The stored event's id. */
44
+ eventId: string;
45
+ /** How many devices the push was accepted for. */
46
+ delivered: number;
47
+ }
48
+ export interface SignoOptions {
49
+ /** API base URL. Defaults to the hosted Signo service. */
50
+ apiUrl?: string;
51
+ /** Custom fetch implementation (defaults to the global `fetch`). */
52
+ fetch?: typeof fetch;
53
+ }
54
+ /** Thrown when the Signo API rejects a request. */
55
+ export declare class SignoError extends Error {
56
+ readonly status: number;
57
+ constructor(message: string, status: number);
58
+ }
59
+ /** A Signo client. Sending is public - a topic's namespace is its address. */
60
+ export declare class Signo {
61
+ #private;
62
+ constructor(options?: SignoOptions);
63
+ /** Send a notification to a topic, identified by its namespace. */
64
+ send(namespace: string, message: NotifyOptions): Promise<SendResult>;
65
+ /** Alias for {@link Signo.send}. */
66
+ notify(namespace: string, message: NotifyOptions): Promise<SendResult>;
67
+ }
68
+ /** Zero-config client for the hosted Signo service. */
69
+ export declare const signo: Signo;
70
+ export default signo;
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Signo server SDK - send notifications to your Signo topics from any Node backend.
3
+ *
4
+ * ```ts
5
+ * import { Signo } from '@signo/sdk';
6
+ * const signo = new Signo();
7
+ * await signo.send('a7bx9k', { title: 'New sale 馃帀', body: 'Order #1043 路 $89.00' });
8
+ * ```
9
+ */
10
+ const DEFAULT_API_URL = 'https://signo.digitospace.com';
11
+ /** Thrown when the Signo API rejects a request. */
12
+ export class SignoError extends Error {
13
+ status;
14
+ constructor(message, status) {
15
+ super(message);
16
+ this.name = 'SignoError';
17
+ this.status = status;
18
+ }
19
+ }
20
+ /** Accept a namespace with or without its `signo.` prefix. */
21
+ function address(namespace) {
22
+ return /^signo\./i.test(namespace) ? namespace : `signo.${namespace}`;
23
+ }
24
+ /** A Signo client. Sending is public - a topic's namespace is its address. */
25
+ export class Signo {
26
+ #apiUrl;
27
+ #fetch;
28
+ constructor(options = {}) {
29
+ this.#apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, '');
30
+ const fetchImpl = options.fetch ?? globalThis.fetch;
31
+ if (!fetchImpl) {
32
+ throw new Error('No global fetch found - use Node >=18 or pass options.fetch.');
33
+ }
34
+ this.#fetch = fetchImpl;
35
+ }
36
+ /** Send a notification to a topic, identified by its namespace. */
37
+ async send(namespace, message) {
38
+ if (!namespace)
39
+ throw new Error('A topic namespace is required.');
40
+ if (!message?.title)
41
+ throw new Error('A notification title is required.');
42
+ const res = await this.#fetch(`${this.#apiUrl}/api/events`, {
43
+ method: 'POST',
44
+ headers: { 'content-type': 'application/json' },
45
+ body: JSON.stringify({ namespace: address(namespace), ...message }),
46
+ });
47
+ let data = null;
48
+ try {
49
+ data = await res.json();
50
+ }
51
+ catch {
52
+ // Non-JSON body (e.g. a gateway error page); fall through to the status check.
53
+ }
54
+ if (res.status !== 202) {
55
+ const error = data?.error;
56
+ throw new SignoError(error ?? `Signo request failed (HTTP ${res.status}).`, res.status);
57
+ }
58
+ return data;
59
+ }
60
+ /** Alias for {@link Signo.send}. */
61
+ notify(namespace, message) {
62
+ return this.send(namespace, message);
63
+ }
64
+ }
65
+ /** Zero-config client for the hosted Signo service. */
66
+ export const signo = new Signo();
67
+ export default signo;
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@signo/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Send Signo notifications from your Node backend.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "prepublishOnly": "tsc -p tsconfig.json"
21
+ },
22
+ "keywords": [
23
+ "signo",
24
+ "notifications",
25
+ "push"
26
+ ],
27
+ "license": "MIT",
28
+ "homepage": "https://signo.digitospace.com",
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5"
34
+ }
35
+ }