@hipnation-truth/sdk 0.6.0 → 0.7.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/dist/index.js CHANGED
@@ -72,6 +72,8 @@ __export(src_exports, {
72
72
  NOTIFICATION_EVENTS: () => NOTIFICATION_EVENTS,
73
73
  NotesError: () => NotesError,
74
74
  NotesResource: () => NotesResource,
75
+ NotificationsError: () => NotificationsError,
76
+ NotificationsResource: () => NotificationsResource,
75
77
  PROVIDER_EVENTS: () => PROVIDER_EVENTS,
76
78
  PatientDetailsError: () => PatientDetailsError,
77
79
  PatientDetailsResource: () => PatientDetailsResource,
@@ -87,7 +89,12 @@ __export(src_exports, {
87
89
  TranslationError: () => TranslationError,
88
90
  TranslationResource: () => TranslationResource,
89
91
  TruthClient: () => TruthClient,
90
- generateUuidV7: () => generateUuidV7
92
+ generateUuidV7: () => generateUuidV7,
93
+ isWebPushSupported: () => isWebPushSupported,
94
+ onServiceWorkerMessage: () => onServiceWorkerMessage,
95
+ registerServiceWorker: () => registerServiceWorker,
96
+ subscribeToPush: () => subscribeToPush,
97
+ subscriptionToJSON: () => subscriptionToJSON
91
98
  });
92
99
  module.exports = __toCommonJS(src_exports);
93
100
 
@@ -627,6 +634,188 @@ var NotesResource = class {
627
634
  }
628
635
  };
629
636
 
637
+ // src/resources/notifications.ts
638
+ var NotificationsError = class extends Error {
639
+ constructor(operation, status, message) {
640
+ super(message != null ? message : `Notifications ${operation} failed (HTTP ${status})`);
641
+ this.name = "NotificationsError";
642
+ this.status = status;
643
+ }
644
+ };
645
+ var NotificationsResource = class {
646
+ constructor(apiBaseUrl, apiKey) {
647
+ this.baseUrl = apiBaseUrl;
648
+ this.apiKey = apiKey;
649
+ }
650
+ post(path, body) {
651
+ return __async(this, null, function* () {
652
+ const res = yield fetch(`${this.baseUrl}/api${path}`, {
653
+ method: "POST",
654
+ headers: {
655
+ "Content-Type": "application/json",
656
+ Accept: "application/json",
657
+ "X-API-Key": this.apiKey
658
+ },
659
+ body: JSON.stringify(body)
660
+ });
661
+ if (!res.ok) {
662
+ const text = yield res.text().catch(() => "");
663
+ throw new NotificationsError(path, res.status, text.slice(0, 200));
664
+ }
665
+ return yield res.json();
666
+ });
667
+ }
668
+ get(path, params) {
669
+ return __async(this, null, function* () {
670
+ const url = new URL(`${this.baseUrl}/api${path}`);
671
+ for (const [k, v] of Object.entries(params)) {
672
+ url.searchParams.set(k, v);
673
+ }
674
+ const res = yield fetch(url.toString(), {
675
+ method: "GET",
676
+ headers: {
677
+ Accept: "application/json",
678
+ "X-API-Key": this.apiKey
679
+ }
680
+ });
681
+ if (!res.ok) {
682
+ const text = yield res.text().catch(() => "");
683
+ throw new NotificationsError(path, res.status, text.slice(0, 200));
684
+ }
685
+ return yield res.json();
686
+ });
687
+ }
688
+ delete(path) {
689
+ return __async(this, null, function* () {
690
+ const res = yield fetch(`${this.baseUrl}/api${path}`, {
691
+ method: "DELETE",
692
+ headers: {
693
+ Accept: "application/json",
694
+ "X-API-Key": this.apiKey
695
+ }
696
+ });
697
+ if (!res.ok) {
698
+ const text = yield res.text().catch(() => "");
699
+ throw new NotificationsError(path, res.status, text.slice(0, 200));
700
+ }
701
+ return yield res.json();
702
+ });
703
+ }
704
+ /**
705
+ * Register a device (or refresh its metadata) for push delivery.
706
+ * Safe to call repeatedly — the server dedupes by native token.
707
+ */
708
+ registerDevice(input) {
709
+ return __async(this, null, function* () {
710
+ return this.post("/notifications/devices/register", input);
711
+ });
712
+ }
713
+ /** Revoke a device — on sign-out or when the OS reports an invalid token. */
714
+ unregisterDevice(input) {
715
+ return __async(this, null, function* () {
716
+ return this.post("/notifications/devices/unregister", input);
717
+ });
718
+ }
719
+ /**
720
+ * Send a push notification to every active device belonging to
721
+ * `userId`. Honors the user's notificationPreferences (quiet hours,
722
+ * DND, channel off) before publishing.
723
+ */
724
+ send(input) {
725
+ return __async(this, null, function* () {
726
+ return this.post("/notifications/send", input);
727
+ });
728
+ }
729
+ /** Read a user's notification preferences. Returns defaults when no row exists. */
730
+ getPreferences(userId) {
731
+ return __async(this, null, function* () {
732
+ return this.get("/notifications/preferences", { userId });
733
+ });
734
+ }
735
+ updatePreferences(input) {
736
+ return __async(this, null, function* () {
737
+ return this.post("/notifications/preferences", input);
738
+ });
739
+ }
740
+ /**
741
+ * Schedule a future push notification. Convex's native scheduler
742
+ * fires the send at `scheduledAt` and runs the same delivery
743
+ * pipeline as `send()` (preferences, devices, history audit).
744
+ *
745
+ * Throws `NotificationsError` with status 400 if `scheduledAt` is
746
+ * not strictly in the future.
747
+ */
748
+ schedule(input) {
749
+ return __async(this, null, function* () {
750
+ return this.post("/notifications/schedule", input);
751
+ });
752
+ }
753
+ /**
754
+ * Cancel a pending scheduled notification. Returns `cancelled: false`
755
+ * (no error) if the job has already executed, was previously
756
+ * cancelled, or no longer exists — `reason` describes which case.
757
+ */
758
+ cancelScheduled(jobId) {
759
+ return __async(this, null, function* () {
760
+ return this.delete(`/notifications/schedule/${encodeURIComponent(jobId)}`);
761
+ });
762
+ }
763
+ /**
764
+ * List scheduled notifications for a user — pending, executed,
765
+ * cancelled, or failed. Most-recent first. Default limit 100.
766
+ */
767
+ listScheduled(userId, options) {
768
+ return __async(this, null, function* () {
769
+ const params = { userId };
770
+ if ((options == null ? void 0 : options.limit) !== void 0) {
771
+ params.limit = String(options.limit);
772
+ }
773
+ return this.get("/notifications/schedule", params);
774
+ });
775
+ }
776
+ getVapidKey() {
777
+ return __async(this, null, function* () {
778
+ try {
779
+ const result = yield this.get(
780
+ "/notifications/vapid-key",
781
+ {}
782
+ );
783
+ return result.vapidPublicKey;
784
+ } catch (e) {
785
+ return null;
786
+ }
787
+ });
788
+ }
789
+ onPushReceived(callback) {
790
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
791
+ return () => {
792
+ };
793
+ }
794
+ const handler = (event) => {
795
+ var _a;
796
+ if (((_a = event.data) == null ? void 0 : _a.type) === "TRUTH_PUSH_RECEIVED") {
797
+ callback(event.data.payload);
798
+ }
799
+ };
800
+ navigator.serviceWorker.addEventListener("message", handler);
801
+ return () => navigator.serviceWorker.removeEventListener("message", handler);
802
+ }
803
+ onPushTapped(callback) {
804
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
805
+ return () => {
806
+ };
807
+ }
808
+ const handler = (event) => {
809
+ var _a;
810
+ if (((_a = event.data) == null ? void 0 : _a.type) === "TRUTH_PUSH_TAPPED") {
811
+ callback(event.data.payload);
812
+ }
813
+ };
814
+ navigator.serviceWorker.addEventListener("message", handler);
815
+ return () => navigator.serviceWorker.removeEventListener("message", handler);
816
+ }
817
+ };
818
+
630
819
  // src/resources/patient-details.ts
631
820
  var PatientDetailsError = class extends Error {
632
821
  constructor(operation, status, message) {
@@ -1192,6 +1381,65 @@ function sleep(ms) {
1192
1381
  return new Promise((resolve) => setTimeout(resolve, ms));
1193
1382
  }
1194
1383
 
1384
+ // src/web-push.ts
1385
+ function isWebPushSupported() {
1386
+ return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window;
1387
+ }
1388
+ function registerServiceWorker(path = "/truth-sw.js") {
1389
+ return __async(this, null, function* () {
1390
+ return navigator.serviceWorker.register(path);
1391
+ });
1392
+ }
1393
+ function subscribeToPush(registration, vapidPublicKey) {
1394
+ return __async(this, null, function* () {
1395
+ const existing = yield registration.pushManager.getSubscription();
1396
+ if (existing) {
1397
+ return existing;
1398
+ }
1399
+ return registration.pushManager.subscribe({
1400
+ userVisibleOnly: true,
1401
+ applicationServerKey: urlBase64ToUint8Array(
1402
+ vapidPublicKey
1403
+ )
1404
+ });
1405
+ });
1406
+ }
1407
+ function subscriptionToJSON(sub) {
1408
+ var _a, _b, _c, _d;
1409
+ const json = sub.toJSON();
1410
+ return {
1411
+ endpoint: sub.endpoint,
1412
+ keys: {
1413
+ p256dh: (_b = (_a = json.keys) == null ? void 0 : _a.p256dh) != null ? _b : "",
1414
+ auth: (_d = (_c = json.keys) == null ? void 0 : _c.auth) != null ? _d : ""
1415
+ }
1416
+ };
1417
+ }
1418
+ function urlBase64ToUint8Array(base64String) {
1419
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
1420
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
1421
+ const rawData = atob(base64);
1422
+ const outputArray = new Uint8Array(rawData.length);
1423
+ for (let i = 0; i < rawData.length; ++i) {
1424
+ outputArray[i] = rawData.charCodeAt(i);
1425
+ }
1426
+ return outputArray;
1427
+ }
1428
+ function onServiceWorkerMessage(type, callback) {
1429
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
1430
+ return () => {
1431
+ };
1432
+ }
1433
+ const handler = (event) => {
1434
+ var _a;
1435
+ if (((_a = event.data) == null ? void 0 : _a.type) === type) {
1436
+ callback(event.data.payload);
1437
+ }
1438
+ };
1439
+ navigator.serviceWorker.addEventListener("message", handler);
1440
+ return () => navigator.serviceWorker.removeEventListener("message", handler);
1441
+ }
1442
+
1195
1443
  // src/client.ts
1196
1444
  var CONVEX_URLS = {
1197
1445
  local: "https://courteous-duck-623.convex.cloud",
@@ -1203,7 +1451,9 @@ var CONVEX_URLS = {
1203
1451
  };
1204
1452
  var TruthClient = class {
1205
1453
  constructor(config) {
1206
- var _a, _b, _c, _d, _e, _f, _g;
1454
+ this._vapidPublicKey = null;
1455
+ this._webPushReady = null;
1456
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1207
1457
  const convexUrl = (_b = (_a = config.convexUrl) != null ? _a : CONVEX_URLS[config.environment]) != null ? _b : CONVEX_URLS.local;
1208
1458
  this.convex = new import_browser.ConvexHttpClient(convexUrl);
1209
1459
  this.tracker = new Tracker({
@@ -1232,6 +1482,40 @@ var TruthClient = class {
1232
1482
  );
1233
1483
  this.notes = new NotesResource(apiUrl, config.apiKey);
1234
1484
  this.physicians = new PhysiciansResource(this.convex);
1485
+ this.notifications = new NotificationsResource(apiUrl, config.apiKey);
1486
+ this._serviceWorkerPath = (_h = config.serviceWorkerPath) != null ? _h : "/truth-sw.js";
1487
+ if (typeof window !== "undefined" && isWebPushSupported() && config.autoInitServiceWorker !== false) {
1488
+ this._webPushReady = this.initWebPush();
1489
+ }
1490
+ }
1491
+ get vapidPublicKey() {
1492
+ return this._vapidPublicKey;
1493
+ }
1494
+ get webPushReady() {
1495
+ return this._webPushReady;
1496
+ }
1497
+ initWebPush() {
1498
+ return __async(this, null, function* () {
1499
+ try {
1500
+ const key = yield this.notifications.getVapidKey();
1501
+ if (!key) {
1502
+ return;
1503
+ }
1504
+ this._vapidPublicKey = key;
1505
+ const registration = yield registerServiceWorker(this._serviceWorkerPath);
1506
+ yield navigator.serviceWorker.ready;
1507
+ const subscription = yield subscribeToPush(registration, key);
1508
+ const subJSON = subscriptionToJSON(subscription);
1509
+ yield this.notifications.registerDevice({
1510
+ userId: "__pending__",
1511
+ platform: "web",
1512
+ webPushSubscription: subJSON,
1513
+ locale: navigator.language,
1514
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
1515
+ });
1516
+ } catch (e) {
1517
+ }
1518
+ });
1235
1519
  }
1236
1520
  /**
1237
1521
  * The resolved Truth API base URL for this environment.
@@ -1389,6 +1673,8 @@ var ENVIRONMENTS = {
1389
1673
  NOTIFICATION_EVENTS,
1390
1674
  NotesError,
1391
1675
  NotesResource,
1676
+ NotificationsError,
1677
+ NotificationsResource,
1392
1678
  PROVIDER_EVENTS,
1393
1679
  PatientDetailsError,
1394
1680
  PatientDetailsResource,
@@ -1404,6 +1690,11 @@ var ENVIRONMENTS = {
1404
1690
  TranslationError,
1405
1691
  TranslationResource,
1406
1692
  TruthClient,
1407
- generateUuidV7
1693
+ generateUuidV7,
1694
+ isWebPushSupported,
1695
+ onServiceWorkerMessage,
1696
+ registerServiceWorker,
1697
+ subscribeToPush,
1698
+ subscriptionToJSON
1408
1699
  });
1409
1700
  //# sourceMappingURL=index.js.map