@nexushub/client 0.8.6 → 0.8.8

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/dist/react.cjs CHANGED
@@ -2438,6 +2438,89 @@ async function parseError(res) {
2438
2438
  }
2439
2439
  }
2440
2440
 
2441
+ // src/notifications/push-client.ts
2442
+ var NexusPushClient = class {
2443
+ constructor(config) {
2444
+ this.config = config;
2445
+ }
2446
+ /**
2447
+ * Helper to convert Base64 VAPID key to Uint8Array for WebPush security
2448
+ */
2449
+ urlBase64ToUint8Array(base64String) {
2450
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
2451
+ const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/");
2452
+ const rawData = window.atob(base64);
2453
+ const outputArray = new Uint8Array(rawData.length);
2454
+ for (let i = 0; i < rawData.length; ++i) {
2455
+ outputArray[i] = rawData.charCodeAt(i);
2456
+ }
2457
+ return outputArray;
2458
+ }
2459
+ /**
2460
+ * Requests browser notification permissions and registers the WebPush subscription
2461
+ */
2462
+ async requestSubscription(serviceWorkerPath = "/sw.js") {
2463
+ if (typeof window === "undefined" || !("serviceWorker" in navigator) || !("PushManager" in window)) {
2464
+ console.warn(
2465
+ "[NexusHub] Push notifications are not supported in this browser environment."
2466
+ );
2467
+ return false;
2468
+ }
2469
+ try {
2470
+ const permission = await Notification.requestPermission();
2471
+ if (permission !== "granted") {
2472
+ console.warn("[NexusHub] Notification permission denied by user.");
2473
+ return false;
2474
+ }
2475
+ const vapidRes = await fetch(
2476
+ `${this.config.apiUrl}/notifications/vapid-key`,
2477
+ {
2478
+ headers: {
2479
+ Authorization: `Bearer ${this.config.apiKey}`,
2480
+ "x-nexus-project": this.config.projectId
2481
+ }
2482
+ }
2483
+ );
2484
+ if (!vapidRes.ok) throw new Error("Failed to fetch VAPID public key.");
2485
+ const { publicKey } = await vapidRes.json();
2486
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath);
2487
+ await navigator.serviceWorker.ready;
2488
+ const subscription = await registration.pushManager.subscribe({
2489
+ userVisibleOnly: true,
2490
+ // 🚀 RESOLVED: Cast as 'any' to bypass strict DOM BufferSource typings
2491
+ applicationServerKey: this.urlBase64ToUint8Array(publicKey)
2492
+ });
2493
+ const rawSub = subscription.toJSON();
2494
+ if (!rawSub.endpoint || !_optionalChain([rawSub, 'access', _46 => _46.keys, 'optionalAccess', _47 => _47.auth]) || !_optionalChain([rawSub, 'access', _48 => _48.keys, 'optionalAccess', _49 => _49.p256dh])) {
2495
+ throw new Error(
2496
+ "Malformed subscription payload received from browser."
2497
+ );
2498
+ }
2499
+ const res = await fetch(
2500
+ `${this.config.apiUrl}/notifications/project/${this.config.projectId}/subscribe`,
2501
+ {
2502
+ method: "POST",
2503
+ headers: {
2504
+ "Content-Type": "application/json",
2505
+ Authorization: `Bearer ${this.config.apiKey}`,
2506
+ "x-nexus-project": this.config.projectId
2507
+ },
2508
+ body: JSON.stringify({
2509
+ endpoint: rawSub.endpoint,
2510
+ auth: rawSub.keys.auth,
2511
+ p256dh: rawSub.keys.p256dh,
2512
+ provider: "WEB_PUSH"
2513
+ })
2514
+ }
2515
+ );
2516
+ return res.ok;
2517
+ } catch (err) {
2518
+ console.error("[NexusHub] WebPush subscription failed:", err);
2519
+ return false;
2520
+ }
2521
+ }
2522
+ };
2523
+
2441
2524
  // src/components/NexusProvider.tsx
2442
2525
 
2443
2526
  var NexusContext = _react.createContext.call(void 0, nexus);
@@ -2466,11 +2549,10 @@ var NexusProvider = ({
2466
2549
  projectId,
2467
2550
  disableAnalytics = false,
2468
2551
  hasConsent = true,
2469
- // NEW: default true to preserve existing behaviour
2470
2552
  enableLiveFeed = false,
2471
- // NEW
2472
- onLiveEvent
2473
- // NEW
2553
+ onLiveEvent,
2554
+ autoPromptPush = true
2555
+ // 🚀 Default: true for zero-config automatic visitor subscription
2474
2556
  }) => {
2475
2557
  const isInitialized = _react.useRef.call(void 0, false);
2476
2558
  const socketRef = _react.useRef.call(void 0, null);
@@ -2488,6 +2570,29 @@ var NexusProvider = ({
2488
2570
  }
2489
2571
  return nexus.getConfig();
2490
2572
  }, [projectId]);
2573
+ _react.useEffect.call(void 0, () => {
2574
+ if (typeof window === "undefined" || !("serviceWorker" in navigator))
2575
+ return;
2576
+ navigator.serviceWorker.register("/sw.js").then(async (registration) => {
2577
+ if (nexus.getConfig().debug) {
2578
+ console.log("[NexusHub] \u26A1 Sovereign Service Worker Active.");
2579
+ }
2580
+ if (autoPromptPush && "Notification" in window && Notification.permission === "default") {
2581
+ const pushClient = new NexusPushClient(nexus.getConfig());
2582
+ await pushClient.requestSubscription("/sw.js");
2583
+ return;
2584
+ }
2585
+ if ("Notification" in window && Notification.permission === "granted") {
2586
+ try {
2587
+ const pushClient = new NexusPushClient(nexus.getConfig());
2588
+ await pushClient.requestSubscription("/sw.js");
2589
+ } catch (e16) {
2590
+ }
2591
+ }
2592
+ }).catch((err) => {
2593
+ console.warn("[NexusHub] Service Worker registration failed:", err);
2594
+ });
2595
+ }, [autoPromptPush]);
2491
2596
  _react.useEffect.call(void 0, () => {
2492
2597
  if (typeof window === "undefined" || disableAnalytics || !hasConsent)
2493
2598
  return;
@@ -2513,7 +2618,7 @@ var NexusProvider = ({
2513
2618
  try {
2514
2619
  const { io } = await Promise.resolve().then(() => _interopRequireWildcard(require("socket.io-client")));
2515
2620
  const cfg = nexus.getConfig();
2516
- const wsUrl = cfg.apiUrl || "http://localhost:3001";
2621
+ const wsUrl = cfg.apiUrl || "https://gnapex.co.tz";
2517
2622
  const socket = io(`${wsUrl}/analytics`, {
2518
2623
  auth: { token: cfg.apiKey },
2519
2624
  query: { projectId: cfg.projectId },
@@ -2531,7 +2636,7 @@ var NexusProvider = ({
2531
2636
  });
2532
2637
  socket.on("live_event", (event) => {
2533
2638
  setLatestEvent(event);
2534
- _optionalChain([onLiveEvent, 'optionalCall', _46 => _46(event)]);
2639
+ _optionalChain([onLiveEvent, 'optionalCall', _50 => _50(event)]);
2535
2640
  });
2536
2641
  socketRef.current = socket;
2537
2642
  } catch (err) {
@@ -2694,7 +2799,7 @@ function NexusVideo({
2694
2799
  const isYouTube = value.includes("youtube.com") || value.includes("youtu.be");
2695
2800
  const isVimeo = value.includes("vimeo.com");
2696
2801
  if (isYouTube) {
2697
- const videoId = value.includes("youtu.be") ? value.split("/").pop() : _optionalChain([value, 'access', _47 => _47.split, 'call', _48 => _48("v="), 'access', _49 => _49[1], 'optionalAccess', _50 => _50.split, 'call', _51 => _51("&"), 'access', _52 => _52[0]]);
2802
+ const videoId = value.includes("youtu.be") ? value.split("/").pop() : _optionalChain([value, 'access', _51 => _51.split, 'call', _52 => _52("v="), 'access', _53 => _53[1], 'optionalAccess', _54 => _54.split, 'call', _55 => _55("&"), 'access', _56 => _56[0]]);
2698
2803
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2699
2804
  "iframe",
2700
2805
  {
package/dist/react.d.cts CHANGED
@@ -237,6 +237,7 @@ interface NexusProviderProps {
237
237
  hasConsent?: boolean;
238
238
  enableLiveFeed?: boolean;
239
239
  onLiveEvent?: (event: LiveAnalyticsEvent) => void;
240
+ autoPromptPush?: boolean;
240
241
  }
241
242
  interface LiveAnalyticsEvent {
242
243
  eventType: string;
@@ -262,7 +263,7 @@ interface LiveFeedContextValue {
262
263
  isConnected: boolean;
263
264
  }
264
265
  declare const useNexusLiveFeed: () => LiveFeedContextValue;
265
- declare const NexusProvider: ({ children, projectId, disableAnalytics, hasConsent, enableLiveFeed, onLiveEvent, }: NexusProviderProps) => react_jsx_runtime.JSX.Element;
266
+ declare const NexusProvider: ({ children, projectId, disableAnalytics, hasConsent, enableLiveFeed, onLiveEvent, autoPromptPush, }: NexusProviderProps) => react_jsx_runtime.JSX.Element;
266
267
  declare const useNexus: () => NexusClient;
267
268
  declare const useNexusAnalytics: () => {
268
269
  track: (eventName: string, properties?: Record<string, any>) => void;
package/dist/react.d.ts CHANGED
@@ -237,6 +237,7 @@ interface NexusProviderProps {
237
237
  hasConsent?: boolean;
238
238
  enableLiveFeed?: boolean;
239
239
  onLiveEvent?: (event: LiveAnalyticsEvent) => void;
240
+ autoPromptPush?: boolean;
240
241
  }
241
242
  interface LiveAnalyticsEvent {
242
243
  eventType: string;
@@ -262,7 +263,7 @@ interface LiveFeedContextValue {
262
263
  isConnected: boolean;
263
264
  }
264
265
  declare const useNexusLiveFeed: () => LiveFeedContextValue;
265
- declare const NexusProvider: ({ children, projectId, disableAnalytics, hasConsent, enableLiveFeed, onLiveEvent, }: NexusProviderProps) => react_jsx_runtime.JSX.Element;
266
+ declare const NexusProvider: ({ children, projectId, disableAnalytics, hasConsent, enableLiveFeed, onLiveEvent, autoPromptPush, }: NexusProviderProps) => react_jsx_runtime.JSX.Element;
266
267
  declare const useNexus: () => NexusClient;
267
268
  declare const useNexusAnalytics: () => {
268
269
  track: (eventName: string, properties?: Record<string, any>) => void;
package/dist/react.js CHANGED
@@ -2438,6 +2438,89 @@ async function parseError(res) {
2438
2438
  }
2439
2439
  }
2440
2440
 
2441
+ // src/notifications/push-client.ts
2442
+ var NexusPushClient = class {
2443
+ constructor(config) {
2444
+ this.config = config;
2445
+ }
2446
+ /**
2447
+ * Helper to convert Base64 VAPID key to Uint8Array for WebPush security
2448
+ */
2449
+ urlBase64ToUint8Array(base64String) {
2450
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
2451
+ const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/");
2452
+ const rawData = window.atob(base64);
2453
+ const outputArray = new Uint8Array(rawData.length);
2454
+ for (let i = 0; i < rawData.length; ++i) {
2455
+ outputArray[i] = rawData.charCodeAt(i);
2456
+ }
2457
+ return outputArray;
2458
+ }
2459
+ /**
2460
+ * Requests browser notification permissions and registers the WebPush subscription
2461
+ */
2462
+ async requestSubscription(serviceWorkerPath = "/sw.js") {
2463
+ if (typeof window === "undefined" || !("serviceWorker" in navigator) || !("PushManager" in window)) {
2464
+ console.warn(
2465
+ "[NexusHub] Push notifications are not supported in this browser environment."
2466
+ );
2467
+ return false;
2468
+ }
2469
+ try {
2470
+ const permission = await Notification.requestPermission();
2471
+ if (permission !== "granted") {
2472
+ console.warn("[NexusHub] Notification permission denied by user.");
2473
+ return false;
2474
+ }
2475
+ const vapidRes = await fetch(
2476
+ `${this.config.apiUrl}/notifications/vapid-key`,
2477
+ {
2478
+ headers: {
2479
+ Authorization: `Bearer ${this.config.apiKey}`,
2480
+ "x-nexus-project": this.config.projectId
2481
+ }
2482
+ }
2483
+ );
2484
+ if (!vapidRes.ok) throw new Error("Failed to fetch VAPID public key.");
2485
+ const { publicKey } = await vapidRes.json();
2486
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath);
2487
+ await navigator.serviceWorker.ready;
2488
+ const subscription = await registration.pushManager.subscribe({
2489
+ userVisibleOnly: true,
2490
+ // 🚀 RESOLVED: Cast as 'any' to bypass strict DOM BufferSource typings
2491
+ applicationServerKey: this.urlBase64ToUint8Array(publicKey)
2492
+ });
2493
+ const rawSub = subscription.toJSON();
2494
+ if (!rawSub.endpoint || !rawSub.keys?.auth || !rawSub.keys?.p256dh) {
2495
+ throw new Error(
2496
+ "Malformed subscription payload received from browser."
2497
+ );
2498
+ }
2499
+ const res = await fetch(
2500
+ `${this.config.apiUrl}/notifications/project/${this.config.projectId}/subscribe`,
2501
+ {
2502
+ method: "POST",
2503
+ headers: {
2504
+ "Content-Type": "application/json",
2505
+ Authorization: `Bearer ${this.config.apiKey}`,
2506
+ "x-nexus-project": this.config.projectId
2507
+ },
2508
+ body: JSON.stringify({
2509
+ endpoint: rawSub.endpoint,
2510
+ auth: rawSub.keys.auth,
2511
+ p256dh: rawSub.keys.p256dh,
2512
+ provider: "WEB_PUSH"
2513
+ })
2514
+ }
2515
+ );
2516
+ return res.ok;
2517
+ } catch (err) {
2518
+ console.error("[NexusHub] WebPush subscription failed:", err);
2519
+ return false;
2520
+ }
2521
+ }
2522
+ };
2523
+
2441
2524
  // src/components/NexusProvider.tsx
2442
2525
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
2443
2526
  var NexusContext = createContext2(nexus);
@@ -2466,11 +2549,10 @@ var NexusProvider = ({
2466
2549
  projectId,
2467
2550
  disableAnalytics = false,
2468
2551
  hasConsent = true,
2469
- // NEW: default true to preserve existing behaviour
2470
2552
  enableLiveFeed = false,
2471
- // NEW
2472
- onLiveEvent
2473
- // NEW
2553
+ onLiveEvent,
2554
+ autoPromptPush = true
2555
+ // 🚀 Default: true for zero-config automatic visitor subscription
2474
2556
  }) => {
2475
2557
  const isInitialized = useRef(false);
2476
2558
  const socketRef = useRef(null);
@@ -2488,6 +2570,29 @@ var NexusProvider = ({
2488
2570
  }
2489
2571
  return nexus.getConfig();
2490
2572
  }, [projectId]);
2573
+ useEffect2(() => {
2574
+ if (typeof window === "undefined" || !("serviceWorker" in navigator))
2575
+ return;
2576
+ navigator.serviceWorker.register("/sw.js").then(async (registration) => {
2577
+ if (nexus.getConfig().debug) {
2578
+ console.log("[NexusHub] \u26A1 Sovereign Service Worker Active.");
2579
+ }
2580
+ if (autoPromptPush && "Notification" in window && Notification.permission === "default") {
2581
+ const pushClient = new NexusPushClient(nexus.getConfig());
2582
+ await pushClient.requestSubscription("/sw.js");
2583
+ return;
2584
+ }
2585
+ if ("Notification" in window && Notification.permission === "granted") {
2586
+ try {
2587
+ const pushClient = new NexusPushClient(nexus.getConfig());
2588
+ await pushClient.requestSubscription("/sw.js");
2589
+ } catch {
2590
+ }
2591
+ }
2592
+ }).catch((err) => {
2593
+ console.warn("[NexusHub] Service Worker registration failed:", err);
2594
+ });
2595
+ }, [autoPromptPush]);
2491
2596
  useEffect2(() => {
2492
2597
  if (typeof window === "undefined" || disableAnalytics || !hasConsent)
2493
2598
  return;
@@ -2513,7 +2618,7 @@ var NexusProvider = ({
2513
2618
  try {
2514
2619
  const { io } = await import("socket.io-client");
2515
2620
  const cfg = nexus.getConfig();
2516
- const wsUrl = cfg.apiUrl || "http://localhost:3001";
2621
+ const wsUrl = cfg.apiUrl || "https://gnapex.co.tz";
2517
2622
  const socket = io(`${wsUrl}/analytics`, {
2518
2623
  auth: { token: cfg.apiKey },
2519
2624
  query: { projectId: cfg.projectId },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexushub/client",
3
3
  "private": false,
4
- "version": "0.8.6",
4
+ "version": "0.8.8",
5
5
  "description": "The God-Tier NexusHub SDK",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",