@webmobix/pulse-leo-sdk 0.0.1

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,150 @@
1
+ # PulseLeo SDK
2
+
3
+ A lightweight browser SDK for integrating PulseLeo Push Notifications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install pulse-leo-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### 1. Initialize
14
+
15
+ Initialize the client with your VAPID Public Key (found in your Dashboard).
16
+
17
+ ```typescript
18
+ import { PushClient } from "pulse-leo-sdk";
19
+
20
+ const pushClient = new PushClient({
21
+ vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
22
+ backendRegisterUrl: "https://api.pulseleo.com/register", // Or your backend proxy
23
+ serviceWorkerPath: "/sw.js", // Default
24
+ tags: {
25
+ category: "news",
26
+ source: "landing_page",
27
+ },
28
+ });
29
+ ```
30
+
31
+ ### 2. Service Worker Setup
32
+
33
+ The SDK requires a service worker to handle push notifications. Choose one of the following options:
34
+
35
+ #### Option A: Copy to Public Directory (Recommended)
36
+
37
+ Service workers must be served from your domain's root to properly control your application. The most reliable method is to copy the service worker file to your public directory.
38
+
39
+ **Method 1: Using postinstall script**
40
+
41
+ Add a postinstall script to your `package.json`:
42
+
43
+ ```json
44
+ {
45
+ "scripts": {
46
+ "postinstall": "cp node_modules/pulse-leo-sdk/dist/sw.js public/sw.js"
47
+ }
48
+ }
49
+ ```
50
+
51
+ **Method 2: Using build tools (Vite example)**
52
+
53
+ Install `vite-plugin-static-copy`:
54
+
55
+ ```bash
56
+ npm install -D vite-plugin-static-copy
57
+ ```
58
+
59
+ Add to your `vite.config.ts`:
60
+
61
+ ```typescript
62
+ import { viteStaticCopy } from 'vite-plugin-static-copy';
63
+
64
+ export default defineConfig({
65
+ plugins: [
66
+ viteStaticCopy({
67
+ targets: [
68
+ {
69
+ src: 'node_modules/pulse-leo-sdk/dist/sw.js',
70
+ dest: '.'
71
+ }
72
+ ]
73
+ })
74
+ ]
75
+ });
76
+ ```
77
+
78
+ Then initialize the client (default path is `/sw.js`):
79
+
80
+ ```typescript
81
+ const pushClient = new PushClient({
82
+ vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
83
+ backendRegisterUrl: "/api/register",
84
+ // serviceWorkerPath: '/sw.js' is the default, no need to specify
85
+ });
86
+ ```
87
+
88
+ > **Note**: The `?url` import method (e.g., `import swUrl from 'pulse-leo-sdk/sw.js?url'`) is not recommended for service workers due to scope restrictions during development. Use the copy method instead.
89
+
90
+ #### Option B: Custom Service Worker
91
+
92
+ If you need to add your own service worker logic, you can import the SDK's service worker in your own:
93
+
94
+ ```typescript
95
+ // In your custom-sw.js
96
+ import "pulse-leo-sdk/sw";
97
+
98
+ // Add your own service worker logic here
99
+ self.addEventListener("install", (event) => {
100
+ console.log("Custom service worker installed");
101
+ });
102
+ ```
103
+
104
+ Then set `serviceWorkerPath` to your custom service worker:
105
+
106
+ ```typescript
107
+ const pushClient = new PushClient({
108
+ vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
109
+ backendRegisterUrl: "/api/register",
110
+ serviceWorkerPath: "/custom-sw.js",
111
+ });
112
+ ```
113
+
114
+ ### 3. Register for Notifications
115
+
116
+ Call the `register()` method, typically in response to a user action (like a button click) to request permission.
117
+
118
+ ```typescript
119
+ const button = document.getElementById("enable-push");
120
+ button.addEventListener("click", async () => {
121
+ const success = await pushClient.register();
122
+ if (success) {
123
+ console.log("Successfully subscribed!");
124
+ } else {
125
+ console.error("Failed to subscribe.");
126
+ }
127
+ });
128
+ ```
129
+
130
+ ### 4. Check Subscription Status
131
+
132
+ ```typescript
133
+ const isSubscribed = await pushClient.isSubscribed();
134
+ if (isSubscribed) {
135
+ // Update UI to show "Subscribed"
136
+ }
137
+ ```
138
+
139
+ ## Configuration Options
140
+
141
+ | Option | Type | Required | Description |
142
+ | -------------------- | ------------------------ | -------- | ----------------------------------------------------------------- |
143
+ | `vapidPublicKey` | `string` | Yes | Your VAPID Public Key. |
144
+ | `backendRegisterUrl` | `string` | Yes | Endpoint to send subscription data to. |
145
+ | `serviceWorkerPath` | `string` | No | Path to your service worker (default: `/sw.js`). |
146
+ | `tags` | `Record<string, string>` | No | Tags to associate with the subscriber (e.g. `{ topic: "news" }`). |
147
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1,48 @@
1
+ import { PushClientOptions } from './types';
2
+ export * from './types';
3
+ export * from './utils';
4
+ /**
5
+ * Client for managing push notifications.
6
+ */
7
+ export declare class PushClient {
8
+ private config;
9
+ /**
10
+ * Creates an instance of PushClient.
11
+ * @param config - The configuration options for the client.
12
+ */
13
+ constructor(config: PushClientOptions);
14
+ /**
15
+ * Checks if the client is already subscribed to push notifications.
16
+ * @returns A promise that resolves to true if subscribed, false otherwise.
17
+ */
18
+ isSubscribed(): Promise<boolean>;
19
+ /**
20
+ * Registers the client for push notifications.
21
+ * Checks support, config, and existing subscription.
22
+ * Requests permission and registers with backend if necessary.
23
+ * @returns A promise that resolves to true if registration is successful (or already exists), false otherwise.
24
+ */
25
+ register(): Promise<boolean>;
26
+ /**
27
+ * Checks if push notifications are supported in the browser.
28
+ * @returns True if supported, false otherwise.
29
+ */
30
+ private isSupported;
31
+ /**
32
+ * Validates the configuration.
33
+ * @returns True if valid, false otherwise.
34
+ */
35
+ private validateConfig;
36
+ /**
37
+ * Registers the service worker.
38
+ * @returns A promise that resolves with the ServiceWorkerRegistration.
39
+ */
40
+ private registerServiceWorker;
41
+ /**
42
+ * Sends the subscription to the backend.
43
+ * @param subscription - The push subscription object.
44
+ * @returns A promise that resolves when the subscription is sent.
45
+ * @throws Will throw an error if the backend request fails.
46
+ */
47
+ private sendToBackend;
48
+ }
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ function o(n) {
2
+ const e = "=".repeat((4 - n.length % 4) % 4), i = (n + e).replace(/-/g, "+").replace(/_/g, "/"), r = atob(i), s = new Uint8Array(r.length);
3
+ for (let t = 0; t < r.length; ++t)
4
+ s[t] = r.charCodeAt(t);
5
+ return s;
6
+ }
7
+ function a(n) {
8
+ const e = new Uint8Array(n);
9
+ let i = "";
10
+ for (let r = 0; r < e.byteLength; r++)
11
+ i += String.fromCharCode(e[r]);
12
+ return btoa(i).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
13
+ }
14
+ class c {
15
+ config;
16
+ /**
17
+ * Creates an instance of PushClient.
18
+ * @param config - The configuration options for the client.
19
+ */
20
+ constructor(e) {
21
+ console.debug("PushClient initialized"), this.config = e;
22
+ }
23
+ /**
24
+ * Checks if the client is already subscribed to push notifications.
25
+ * @returns A promise that resolves to true if subscribed, false otherwise.
26
+ */
27
+ async isSubscribed() {
28
+ if (!this.isSupported())
29
+ return !1;
30
+ const e = await navigator.serviceWorker.getRegistration();
31
+ return e ? !!await e.pushManager.getSubscription() : !1;
32
+ }
33
+ /**
34
+ * Registers the client for push notifications.
35
+ * Checks support, config, and existing subscription.
36
+ * Requests permission and registers with backend if necessary.
37
+ * @returns A promise that resolves to true if registration is successful (or already exists), false otherwise.
38
+ */
39
+ async register() {
40
+ console.debug("Registering push notifications...");
41
+ try {
42
+ if (!this.isSupported())
43
+ return console.warn("Push notifications are not supported in this browser."), !1;
44
+ if (!this.validateConfig())
45
+ return console.error("Invalid PushClient configuration."), !1;
46
+ const e = await this.registerServiceWorker();
47
+ if (await e.pushManager.getSubscription())
48
+ return !0;
49
+ const r = await Notification.requestPermission();
50
+ if (console.debug(`Notification permission: ${r}`), r !== "granted")
51
+ return console.warn("Notification permission denied."), !1;
52
+ const s = o(
53
+ this.config.vapidPublicKey
54
+ ), t = await e.pushManager.subscribe({
55
+ userVisibleOnly: !0,
56
+ applicationServerKey: s
57
+ });
58
+ return await this.sendToBackend(t), !0;
59
+ } catch (e) {
60
+ return console.error("Failed to register push client:", e), !1;
61
+ }
62
+ }
63
+ /**
64
+ * Checks if push notifications are supported in the browser.
65
+ * @returns True if supported, false otherwise.
66
+ */
67
+ isSupported() {
68
+ return typeof window < "u" && "serviceWorker" in navigator && !!navigator.serviceWorker && "PushManager" in window && "Notification" in window;
69
+ }
70
+ /**
71
+ * Validates the configuration.
72
+ * @returns True if valid, false otherwise.
73
+ */
74
+ validateConfig() {
75
+ return !!(this.config.vapidPublicKey && this.config.backendRegisterUrl);
76
+ }
77
+ /**
78
+ * Registers the service worker.
79
+ * @returns A promise that resolves with the ServiceWorkerRegistration.
80
+ */
81
+ async registerServiceWorker() {
82
+ const e = this.config.serviceWorkerPath || "/sw.js";
83
+ console.debug(`[PushClient] Registering service worker at: ${e}`);
84
+ try {
85
+ const i = await navigator.serviceWorker.register(e, {
86
+ scope: "/"
87
+ });
88
+ console.debug("[PushClient] Service Worker registered:", i), console.debug(
89
+ `[PushClient] SW state: ${i.installing ? "installing" : i.waiting ? "waiting" : i.active ? "active" : "unknown"}`
90
+ );
91
+ const r = await navigator.serviceWorker.ready;
92
+ return console.debug("[PushClient] Service Worker ready"), r;
93
+ } catch (i) {
94
+ throw console.error("[PushClient] Failed to register service worker:", i), i;
95
+ }
96
+ }
97
+ /**
98
+ * Sends the subscription to the backend.
99
+ * @param subscription - The push subscription object.
100
+ * @returns A promise that resolves when the subscription is sent.
101
+ * @throws Will throw an error if the backend request fails.
102
+ */
103
+ async sendToBackend(e) {
104
+ console.debug("[PushClient] Sending subscription to backend", e);
105
+ const i = {
106
+ ...e.toJSON(),
107
+ tags: this.config.tags
108
+ }, r = await fetch(this.config.backendRegisterUrl, {
109
+ method: "POST",
110
+ headers: {
111
+ "Content-Type": "application/json"
112
+ },
113
+ body: JSON.stringify(i)
114
+ });
115
+ if (!r.ok)
116
+ throw new Error(
117
+ `Failed to register push subscription: ${r.statusText}`
118
+ );
119
+ }
120
+ }
121
+ export {
122
+ c as PushClient,
123
+ a as arrayBufferToUrlBase64,
124
+ o as urlBase64ToUint8Array
125
+ };
package/dist/sw.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/sw.js ADDED
@@ -0,0 +1,55 @@
1
+ const n = self;
2
+ n.addEventListener("install", (i) => {
3
+ console.log("[SW] Service Worker installing..."), i.waitUntil(n.skipWaiting());
4
+ });
5
+ n.addEventListener("activate", (i) => {
6
+ console.log("[SW] Service Worker activated"), i.waitUntil(n.clients.claim());
7
+ });
8
+ n.addEventListener("push", (i) => {
9
+ if (!i.data) {
10
+ console.warn("Push event received with no data.");
11
+ return;
12
+ }
13
+ try {
14
+ const t = i.data.json(), o = t.title || "Notification", a = {
15
+ body: t.body,
16
+ icon: t.icon,
17
+ badge: t.badge,
18
+ data: t.data,
19
+ tag: t.tag,
20
+ renotify: t.renotify,
21
+ requireInteraction: t.requireInteraction,
22
+ silent: t.silent,
23
+ timestamp: t.timestamp,
24
+ actions: t.actions,
25
+ dir: t.dir,
26
+ lang: t.lang,
27
+ vibrate: t.vibrate,
28
+ image: t.image
29
+ };
30
+ i.waitUntil(n.registration.showNotification(o, a));
31
+ } catch (t) {
32
+ console.error("Error handling push event:", t);
33
+ }
34
+ });
35
+ n.addEventListener("notificationclick", (i) => {
36
+ i.notification.close();
37
+ const t = i.notification.data?.url || "/", o = n.clients.matchAll({
38
+ type: "window",
39
+ includeUncontrolled: !0
40
+ }).then((a) => {
41
+ let e = null;
42
+ for (const r of a) {
43
+ const l = new URL(r.url, self.location.href), c = new URL(t, self.location.href);
44
+ if (l.href === c.href) {
45
+ e = r;
46
+ break;
47
+ }
48
+ }
49
+ if (e && "focus" in e)
50
+ return e.focus();
51
+ if (n.clients.openWindow)
52
+ return n.clients.openWindow(t);
53
+ });
54
+ i.waitUntil(o);
55
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Options for initializing the PushClient
3
+ */
4
+ export interface PushClientOptions {
5
+ /**
6
+ * The public VAPID key for the push service.
7
+ */
8
+ vapidPublicKey: string;
9
+ /**
10
+ * The path to the service worker file (e.g., "/sw.js").
11
+ * Defaults to "/sw.js" if not provided.
12
+ */
13
+ serviceWorkerPath?: string;
14
+ /**
15
+ * The URL of your backend endpoint that registers the subscription with PulseLeo.
16
+ */
17
+ backendRegisterUrl: string;
18
+ /**
19
+ * Optional tags to associate with the subscription (key-value pairs).
20
+ * Example: { category: "news", plan: "premium" }
21
+ */
22
+ tags?: Record<string, string>;
23
+ }
24
+ /**
25
+ * Data structure required for push subscription
26
+ */
27
+ export interface PushSubscriptionData {
28
+ endpoint: string;
29
+ expirationTime?: number | null;
30
+ keys: {
31
+ p256dh: string;
32
+ auth: string;
33
+ };
34
+ tags?: Record<string, string>;
35
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Converts a VAPID public key (base64url string) to a Uint8Array.
3
+ * @param base64String The base64url encoded VAPID public key.
4
+ * @returns The Uint8Array representation of the key.
5
+ */
6
+ export declare function urlBase64ToUint8Array(base64String: string): Uint8Array;
7
+ /**
8
+ * Converts an ArrayBuffer or Uint8Array to a base64url string.
9
+ * @param buffer The buffer to convert.
10
+ * @returns The base64url string.
11
+ */
12
+ export declare function arrayBufferToUrlBase64(buffer: ArrayBuffer | Uint8Array): string;
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@webmobix/pulse-leo-sdk",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./sw.js": "./dist/sw.js",
14
+ "./sw": "./dist/sw.js"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "devDependencies": {
20
+ "@playwright/test": "^1.56.1",
21
+ "@types/node": "^22.19.1",
22
+ "happy-dom": "^20.0.10",
23
+ "typescript": "^5.9.3",
24
+ "vite": "^7.2.4",
25
+ "vite-plugin-dts": "^4.5.4",
26
+ "vitest": "^4.0.13"
27
+ },
28
+ "scripts": {
29
+ "dev": "vite",
30
+ "build": "tsc && tsc -p tsconfig.worker.json && vite build",
31
+ "test": "vitest"
32
+ }
33
+ }