@realtimexsco/live-chat 1.4.0 → 1.4.2

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.
@@ -0,0 +1,68 @@
1
+ /* RealtimeX live-chat push service worker (FCM).
2
+ *
3
+ * Copy this file into your app's public root so it is served at
4
+ * /firebase-messaging-sw.js:
5
+ *
6
+ * cp node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js public/
7
+ *
8
+ * Do NOT edit the Firebase config into this file — the chat package passes it
9
+ * via the registration URL (?config=...), so the copy stays generic.
10
+ *
11
+ * If your app already has its own service worker at the root scope, merge the
12
+ * contents of this file into it instead (two workers cannot share a scope).
13
+ */
14
+
15
+ /* global firebase, importScripts */
16
+
17
+ importScripts(
18
+ "https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js",
19
+ );
20
+ importScripts(
21
+ "https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js",
22
+ );
23
+
24
+ var params = new URL(self.location.href).searchParams;
25
+ var rawConfig = params.get("config");
26
+
27
+ if (rawConfig) {
28
+ try {
29
+ var firebaseConfig = JSON.parse(rawConfig);
30
+
31
+ firebase.initializeApp(firebaseConfig);
32
+
33
+ // Background messages that carry a `notification` payload are displayed
34
+ // by FCM automatically; initializing messaging here is what enables it.
35
+ firebase.messaging();
36
+ } catch (error) {
37
+ console.error("[realtimex] Invalid firebase config in SW URL:", error);
38
+ }
39
+ } else {
40
+ console.warn(
41
+ "[realtimex] firebase-messaging-sw.js registered without ?config= — " +
42
+ "push notifications will not be delivered",
43
+ );
44
+ }
45
+
46
+ // Focus (or open) the chat when a notification is clicked.
47
+ self.addEventListener("notificationclick", function (event) {
48
+ event.notification.close();
49
+
50
+ var targetUrl =
51
+ (event.notification.data &&
52
+ event.notification.data.FCM_MSG &&
53
+ event.notification.data.FCM_MSG.data &&
54
+ event.notification.data.FCM_MSG.data.link) ||
55
+ "/";
56
+
57
+ event.waitUntil(
58
+ clients
59
+ .matchAll({ type: "window", includeUncontrolled: true })
60
+ .then(function (windowClients) {
61
+ for (var i = 0; i < windowClients.length; i++) {
62
+ if ("focus" in windowClients[i]) return windowClients[i].focus();
63
+ }
64
+ if (clients.openWindow) return clients.openWindow(targetUrl);
65
+ return undefined;
66
+ }),
67
+ );
68
+ });
package/dist/index.cjs CHANGED
@@ -17777,6 +17777,279 @@ var packageDefaultComponents = {
17777
17777
  ChatSocketStatus: ChatSocketStatus_default
17778
17778
  };
17779
17779
 
17780
+ // src/notifications/push.service.ts
17781
+ init_client();
17782
+ var PUSH_TOKEN_STORAGE_KEY = "realtimex-push-token";
17783
+ var SERVICE_WORKER_PATH = "/firebase-messaging-sw.js";
17784
+ var v2Url = "https://realtimex.softwareco.com/api/v2";
17785
+ var apiGetPushConfig = async () => {
17786
+ const { data } = await apiClient.get(`${v2Url}/notifications/push/config`);
17787
+ return data?.data;
17788
+ };
17789
+ var apiRegisterPushToken = async (token, deviceLabel) => {
17790
+ const { data } = await apiClient.post(`${v2Url}/notifications/push/token`, {
17791
+ token,
17792
+ deviceLabel
17793
+ });
17794
+ return data?.data;
17795
+ };
17796
+ var apiRemovePushToken = async (token) => {
17797
+ const { data } = await apiClient.delete(`${v2Url}/notifications/push/token`, {
17798
+ data: { token }
17799
+ });
17800
+ return data;
17801
+ };
17802
+ var apiGetPushPreference = async () => {
17803
+ const { data } = await apiClient.get(
17804
+ `${v2Url}/notifications/push/preference`
17805
+ );
17806
+ return data?.data;
17807
+ };
17808
+ var apiUpdatePushPreference = async (enabled) => {
17809
+ const { data } = await apiClient.patch(
17810
+ `${v2Url}/notifications/push/preference`,
17811
+ { enabled }
17812
+ );
17813
+ return data?.data;
17814
+ };
17815
+ var isPushSupported = () => typeof window !== "undefined" && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
17816
+ var getStoredPushToken = () => {
17817
+ try {
17818
+ return localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
17819
+ } catch {
17820
+ return null;
17821
+ }
17822
+ };
17823
+ var storePushToken = (token) => {
17824
+ try {
17825
+ if (token) localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
17826
+ else localStorage.removeItem(PUSH_TOKEN_STORAGE_KEY);
17827
+ } catch {
17828
+ }
17829
+ };
17830
+ var defaultDeviceLabel = () => {
17831
+ const ua = navigator.userAgent;
17832
+ const browser = /edg\//i.test(ua) ? "Edge" : /chrome/i.test(ua) ? "Chrome" : /safari/i.test(ua) ? "Safari" : /firefox/i.test(ua) ? "Firefox" : "Browser";
17833
+ const os = /mac/i.test(ua) ? "macOS" : /windows/i.test(ua) ? "Windows" : /android/i.test(ua) ? "Android" : /iphone|ipad/i.test(ua) ? "iOS" : /linux/i.test(ua) ? "Linux" : "Unknown OS";
17834
+ return `${browser} on ${os}`;
17835
+ };
17836
+ var loadFirebaseMessaging = async () => {
17837
+ try {
17838
+ const [{ initializeApp, getApps }, messagingModule] = await Promise.all([
17839
+ import('firebase/app'),
17840
+ import('firebase/messaging')
17841
+ ]);
17842
+ return { initializeApp, getApps, ...messagingModule };
17843
+ } catch {
17844
+ throw new Error(
17845
+ 'Push notifications need the "firebase" package \u2014 run: npm install firebase'
17846
+ );
17847
+ }
17848
+ };
17849
+ var getFirebaseMessaging = async (firebaseConfig) => {
17850
+ const fb = await loadFirebaseMessaging();
17851
+ const app = fb.getApps().length ? fb.getApps()[0] : fb.initializeApp(firebaseConfig);
17852
+ return { fb, messaging: fb.getMessaging(app) };
17853
+ };
17854
+ var enablePushOnThisDevice = async () => {
17855
+ if (!isPushSupported()) {
17856
+ throw new Error("Push notifications are not supported in this browser");
17857
+ }
17858
+ const config = await apiGetPushConfig();
17859
+ if (!config?.enabled) {
17860
+ throw new Error("Push notifications are disabled for this workspace");
17861
+ }
17862
+ if (!config.configured || !config.firebaseConfig || !config.vapidKey) {
17863
+ throw new Error(
17864
+ "Push notifications are not configured on the server yet"
17865
+ );
17866
+ }
17867
+ const permission = await Notification.requestPermission();
17868
+ if (permission !== "granted") {
17869
+ throw new Error("Notification permission was not granted");
17870
+ }
17871
+ let registration;
17872
+ try {
17873
+ registration = await navigator.serviceWorker.register(
17874
+ `${SERVICE_WORKER_PATH}?config=${encodeURIComponent(
17875
+ JSON.stringify(config.firebaseConfig)
17876
+ )}`
17877
+ );
17878
+ } catch {
17879
+ throw new Error(
17880
+ `Could not register ${SERVICE_WORKER_PATH} \u2014 copy it from node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js into your app's public folder`
17881
+ );
17882
+ }
17883
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17884
+ const token = await fb.getToken(messaging, {
17885
+ vapidKey: config.vapidKey,
17886
+ serviceWorkerRegistration: registration
17887
+ });
17888
+ if (!token) {
17889
+ throw new Error("Could not obtain a push token from Firebase");
17890
+ }
17891
+ await apiRegisterPushToken(token, defaultDeviceLabel());
17892
+ storePushToken(token);
17893
+ return token;
17894
+ };
17895
+ var disablePushOnThisDevice = async () => {
17896
+ const token = getStoredPushToken();
17897
+ if (!token) return;
17898
+ try {
17899
+ const config = await apiGetPushConfig();
17900
+ if (config?.firebaseConfig) {
17901
+ const { fb, messaging } = await getFirebaseMessaging(
17902
+ config.firebaseConfig
17903
+ );
17904
+ await fb.deleteToken(messaging).catch(() => void 0);
17905
+ }
17906
+ } catch {
17907
+ }
17908
+ await apiRemovePushToken(token).catch(() => void 0);
17909
+ storePushToken(null);
17910
+ };
17911
+ var onForegroundPush = async (callback) => {
17912
+ const config = await apiGetPushConfig();
17913
+ if (!config?.configured || !config.firebaseConfig) return () => void 0;
17914
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17915
+ return fb.onMessage(messaging, (payload) => {
17916
+ callback({
17917
+ title: payload?.notification?.title,
17918
+ body: payload?.notification?.body,
17919
+ data: payload?.data
17920
+ });
17921
+ });
17922
+ };
17923
+ var usePushNotifications = () => {
17924
+ const [state, setState] = React2.useState({
17925
+ supported: false,
17926
+ allowedByWorkspace: false,
17927
+ configured: false,
17928
+ permission: "unsupported",
17929
+ userEnabled: true,
17930
+ deviceEnabled: false,
17931
+ loading: true,
17932
+ error: null
17933
+ });
17934
+ const refresh = React2.useCallback(async () => {
17935
+ if (!isPushSupported()) {
17936
+ setState((prev) => ({
17937
+ ...prev,
17938
+ supported: false,
17939
+ loading: false
17940
+ }));
17941
+ return;
17942
+ }
17943
+ try {
17944
+ const [config, preference] = await Promise.all([
17945
+ apiGetPushConfig(),
17946
+ apiGetPushPreference().catch(() => null)
17947
+ ]);
17948
+ setState({
17949
+ supported: true,
17950
+ allowedByWorkspace: Boolean(config?.enabled),
17951
+ configured: Boolean(config?.configured),
17952
+ permission: Notification.permission,
17953
+ userEnabled: preference?.enabled ?? true,
17954
+ deviceEnabled: Notification.permission === "granted" && Boolean(getStoredPushToken()),
17955
+ loading: false,
17956
+ error: null
17957
+ });
17958
+ } catch (error) {
17959
+ setState((prev) => ({
17960
+ ...prev,
17961
+ supported: true,
17962
+ loading: false,
17963
+ error: error instanceof Error ? error.message : "Could not load push settings"
17964
+ }));
17965
+ }
17966
+ }, []);
17967
+ React2.useEffect(() => {
17968
+ void refresh();
17969
+ }, [refresh]);
17970
+ const enable = React2.useCallback(async () => {
17971
+ setState((prev) => ({ ...prev, loading: true, error: null }));
17972
+ try {
17973
+ await enablePushOnThisDevice();
17974
+ await refresh();
17975
+ return true;
17976
+ } catch (error) {
17977
+ setState((prev) => ({
17978
+ ...prev,
17979
+ loading: false,
17980
+ permission: isPushSupported() ? Notification.permission : "unsupported",
17981
+ error: error instanceof Error ? error.message : "Could not enable push notifications"
17982
+ }));
17983
+ return false;
17984
+ }
17985
+ }, [refresh]);
17986
+ const disable = React2.useCallback(async () => {
17987
+ setState((prev) => ({ ...prev, loading: true, error: null }));
17988
+ try {
17989
+ await disablePushOnThisDevice();
17990
+ } finally {
17991
+ await refresh();
17992
+ }
17993
+ }, [refresh]);
17994
+ const setUserEnabled = React2.useCallback(
17995
+ async (enabled) => {
17996
+ setState((prev) => ({ ...prev, userEnabled: enabled }));
17997
+ try {
17998
+ await apiUpdatePushPreference(enabled);
17999
+ } catch (error) {
18000
+ setState((prev) => ({
18001
+ ...prev,
18002
+ userEnabled: !enabled,
18003
+ error: error instanceof Error ? error.message : "Could not update push preference"
18004
+ }));
18005
+ }
18006
+ },
18007
+ []
18008
+ );
18009
+ return { ...state, enable, disable, setUserEnabled, refresh };
18010
+ };
18011
+ var PushNotificationToggle = ({
18012
+ className,
18013
+ label = "Push notifications",
18014
+ description = "Get notified about new messages on this device"
18015
+ }) => {
18016
+ const push = usePushNotifications();
18017
+ if (!push.supported || !push.allowedByWorkspace || !push.configured) {
18018
+ return null;
18019
+ }
18020
+ const isOn = push.deviceEnabled && push.userEnabled;
18021
+ const permissionBlocked = push.permission === "denied";
18022
+ const handleChange = async (checked) => {
18023
+ if (checked) {
18024
+ if (!push.userEnabled) await push.setUserEnabled(true);
18025
+ if (!push.deviceEnabled) await push.enable();
18026
+ } else {
18027
+ await push.disable();
18028
+ }
18029
+ };
18030
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18031
+ "div",
18032
+ {
18033
+ className: className ?? "flex items-center justify-between gap-4 rounded-xl border border-border bg-background px-4 py-3",
18034
+ children: [
18035
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
18036
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: label }),
18037
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description })
18038
+ ] }),
18039
+ /* @__PURE__ */ jsxRuntime.jsx(
18040
+ Switch,
18041
+ {
18042
+ checked: isOn,
18043
+ disabled: push.loading || permissionBlocked,
18044
+ onCheckedChange: (checked) => void handleChange(checked),
18045
+ "aria-label": `Toggle ${label}`
18046
+ }
18047
+ )
18048
+ ]
18049
+ }
18050
+ );
18051
+ };
18052
+
17780
18053
  // src/index.ts
17781
18054
  var index_default = ChatMain_default;
17782
18055
 
@@ -17805,6 +18078,12 @@ exports.LoggedUserDetails = LoggedUserDetails_default;
17805
18078
  exports.MessageInput = ChannelMessageBoxView_default;
17806
18079
  exports.MessageItem = MessageItemView;
17807
18080
  exports.MessageList = ActiveChannelMessagesView_default;
18081
+ exports.PushNotificationToggle = PushNotificationToggle;
18082
+ exports.apiGetPushConfig = apiGetPushConfig;
18083
+ exports.apiGetPushPreference = apiGetPushPreference;
18084
+ exports.apiRegisterPushToken = apiRegisterPushToken;
18085
+ exports.apiRemovePushToken = apiRemovePushToken;
18086
+ exports.apiUpdatePushPreference = apiUpdatePushPreference;
17808
18087
  exports.capitalizeFirst = capitalizeFirst;
17809
18088
  exports.clampJumpDate = clampJumpDate;
17810
18089
  exports.default = index_default;
@@ -17812,13 +18091,18 @@ exports.defaultChatClassNames = defaultChatClassNames;
17812
18091
  exports.defaultChatComponents = defaultChatComponents;
17813
18092
  exports.defaultChatFeatures = defaultChatFeatures;
17814
18093
  exports.defaultChatLayoutConfig = defaultChatLayoutConfig;
18094
+ exports.disablePushOnThisDevice = disablePushOnThisDevice;
17815
18095
  exports.downloadFile = downloadFile;
18096
+ exports.enablePushOnThisDevice = enablePushOnThisDevice;
17816
18097
  exports.getFirstAndLastNameOneCharacter = getFirstAndLastNameOneCharacter;
17817
18098
  exports.getResponsiveWidth = getResponsiveWidth;
17818
18099
  exports.getSocketConfig = getSocketConfig;
18100
+ exports.getStoredPushToken = getStoredPushToken;
17819
18101
  exports.isEmpty = isEmpty;
18102
+ exports.isPushSupported = isPushSupported;
17820
18103
  exports.mergeChatCustomization = mergeChatCustomization;
17821
18104
  exports.mergeChatLayoutConfig = mergeChatLayoutConfig;
18105
+ exports.onForegroundPush = onForegroundPush;
17822
18106
  exports.packageDefaultComponents = packageDefaultComponents;
17823
18107
  exports.resolveApiUrl = resolveApiUrl;
17824
18108
  exports.resolveChatApiKey = resolveChatApiKey;
@@ -17840,5 +18124,6 @@ exports.useChatFeatures = useChatFeatures;
17840
18124
  exports.useChatFeaturesOptional = useChatFeaturesOptional;
17841
18125
  exports.useChatLocale = useChatLocale;
17842
18126
  exports.useChatTheme = useChatTheme;
18127
+ exports.usePushNotifications = usePushNotifications;
17843
18128
  //# sourceMappingURL=index.cjs.map
17844
18129
  //# sourceMappingURL=index.cjs.map