@realtimexsco/live-chat 1.4.0 → 1.4.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.
@@ -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,282 @@ 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 = (path) => {
17785
+ const base = (exports.getChatBaseUrl() || String(apiClient.defaults.baseURL || "")).replace(/\/$/, "").replace(/\/v1$/, "/v2");
17786
+ return `${base}${path}`;
17787
+ };
17788
+ var apiGetPushConfig = async () => {
17789
+ const { data } = await apiClient.get(v2Url("/notifications/push/config"));
17790
+ return data?.data;
17791
+ };
17792
+ var apiRegisterPushToken = async (token, deviceLabel) => {
17793
+ const { data } = await apiClient.post(v2Url("/notifications/push/token"), {
17794
+ token,
17795
+ deviceLabel
17796
+ });
17797
+ return data?.data;
17798
+ };
17799
+ var apiRemovePushToken = async (token) => {
17800
+ const { data } = await apiClient.delete(v2Url("/notifications/push/token"), {
17801
+ data: { token }
17802
+ });
17803
+ return data;
17804
+ };
17805
+ var apiGetPushPreference = async () => {
17806
+ const { data } = await apiClient.get(
17807
+ v2Url("/notifications/push/preference")
17808
+ );
17809
+ return data?.data;
17810
+ };
17811
+ var apiUpdatePushPreference = async (enabled) => {
17812
+ const { data } = await apiClient.patch(
17813
+ v2Url("/notifications/push/preference"),
17814
+ { enabled }
17815
+ );
17816
+ return data?.data;
17817
+ };
17818
+ var isPushSupported = () => typeof window !== "undefined" && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
17819
+ var getStoredPushToken = () => {
17820
+ try {
17821
+ return localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
17822
+ } catch {
17823
+ return null;
17824
+ }
17825
+ };
17826
+ var storePushToken = (token) => {
17827
+ try {
17828
+ if (token) localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
17829
+ else localStorage.removeItem(PUSH_TOKEN_STORAGE_KEY);
17830
+ } catch {
17831
+ }
17832
+ };
17833
+ var defaultDeviceLabel = () => {
17834
+ const ua = navigator.userAgent;
17835
+ const browser = /edg\//i.test(ua) ? "Edge" : /chrome/i.test(ua) ? "Chrome" : /safari/i.test(ua) ? "Safari" : /firefox/i.test(ua) ? "Firefox" : "Browser";
17836
+ 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";
17837
+ return `${browser} on ${os}`;
17838
+ };
17839
+ var loadFirebaseMessaging = async () => {
17840
+ try {
17841
+ const [{ initializeApp, getApps }, messagingModule] = await Promise.all([
17842
+ import('firebase/app'),
17843
+ import('firebase/messaging')
17844
+ ]);
17845
+ return { initializeApp, getApps, ...messagingModule };
17846
+ } catch {
17847
+ throw new Error(
17848
+ 'Push notifications need the "firebase" package \u2014 run: npm install firebase'
17849
+ );
17850
+ }
17851
+ };
17852
+ var getFirebaseMessaging = async (firebaseConfig) => {
17853
+ const fb = await loadFirebaseMessaging();
17854
+ const app = fb.getApps().length ? fb.getApps()[0] : fb.initializeApp(firebaseConfig);
17855
+ return { fb, messaging: fb.getMessaging(app) };
17856
+ };
17857
+ var enablePushOnThisDevice = async () => {
17858
+ if (!isPushSupported()) {
17859
+ throw new Error("Push notifications are not supported in this browser");
17860
+ }
17861
+ const config = await apiGetPushConfig();
17862
+ if (!config?.enabled) {
17863
+ throw new Error("Push notifications are disabled for this workspace");
17864
+ }
17865
+ if (!config.configured || !config.firebaseConfig || !config.vapidKey) {
17866
+ throw new Error(
17867
+ "Push notifications are not configured on the server yet"
17868
+ );
17869
+ }
17870
+ const permission = await Notification.requestPermission();
17871
+ if (permission !== "granted") {
17872
+ throw new Error("Notification permission was not granted");
17873
+ }
17874
+ let registration;
17875
+ try {
17876
+ registration = await navigator.serviceWorker.register(
17877
+ `${SERVICE_WORKER_PATH}?config=${encodeURIComponent(
17878
+ JSON.stringify(config.firebaseConfig)
17879
+ )}`
17880
+ );
17881
+ } catch {
17882
+ throw new Error(
17883
+ `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`
17884
+ );
17885
+ }
17886
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17887
+ const token = await fb.getToken(messaging, {
17888
+ vapidKey: config.vapidKey,
17889
+ serviceWorkerRegistration: registration
17890
+ });
17891
+ if (!token) {
17892
+ throw new Error("Could not obtain a push token from Firebase");
17893
+ }
17894
+ await apiRegisterPushToken(token, defaultDeviceLabel());
17895
+ storePushToken(token);
17896
+ return token;
17897
+ };
17898
+ var disablePushOnThisDevice = async () => {
17899
+ const token = getStoredPushToken();
17900
+ if (!token) return;
17901
+ try {
17902
+ const config = await apiGetPushConfig();
17903
+ if (config?.firebaseConfig) {
17904
+ const { fb, messaging } = await getFirebaseMessaging(
17905
+ config.firebaseConfig
17906
+ );
17907
+ await fb.deleteToken(messaging).catch(() => void 0);
17908
+ }
17909
+ } catch {
17910
+ }
17911
+ await apiRemovePushToken(token).catch(() => void 0);
17912
+ storePushToken(null);
17913
+ };
17914
+ var onForegroundPush = async (callback) => {
17915
+ const config = await apiGetPushConfig();
17916
+ if (!config?.configured || !config.firebaseConfig) return () => void 0;
17917
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17918
+ return fb.onMessage(messaging, (payload) => {
17919
+ callback({
17920
+ title: payload?.notification?.title,
17921
+ body: payload?.notification?.body,
17922
+ data: payload?.data
17923
+ });
17924
+ });
17925
+ };
17926
+ var usePushNotifications = () => {
17927
+ const [state, setState] = React2.useState({
17928
+ supported: false,
17929
+ allowedByWorkspace: false,
17930
+ configured: false,
17931
+ permission: "unsupported",
17932
+ userEnabled: true,
17933
+ deviceEnabled: false,
17934
+ loading: true,
17935
+ error: null
17936
+ });
17937
+ const refresh = React2.useCallback(async () => {
17938
+ if (!isPushSupported()) {
17939
+ setState((prev) => ({
17940
+ ...prev,
17941
+ supported: false,
17942
+ loading: false
17943
+ }));
17944
+ return;
17945
+ }
17946
+ try {
17947
+ const [config, preference] = await Promise.all([
17948
+ apiGetPushConfig(),
17949
+ apiGetPushPreference().catch(() => null)
17950
+ ]);
17951
+ setState({
17952
+ supported: true,
17953
+ allowedByWorkspace: Boolean(config?.enabled),
17954
+ configured: Boolean(config?.configured),
17955
+ permission: Notification.permission,
17956
+ userEnabled: preference?.enabled ?? true,
17957
+ deviceEnabled: Notification.permission === "granted" && Boolean(getStoredPushToken()),
17958
+ loading: false,
17959
+ error: null
17960
+ });
17961
+ } catch (error) {
17962
+ setState((prev) => ({
17963
+ ...prev,
17964
+ supported: true,
17965
+ loading: false,
17966
+ error: error instanceof Error ? error.message : "Could not load push settings"
17967
+ }));
17968
+ }
17969
+ }, []);
17970
+ React2.useEffect(() => {
17971
+ void refresh();
17972
+ }, [refresh]);
17973
+ const enable = React2.useCallback(async () => {
17974
+ setState((prev) => ({ ...prev, loading: true, error: null }));
17975
+ try {
17976
+ await enablePushOnThisDevice();
17977
+ await refresh();
17978
+ return true;
17979
+ } catch (error) {
17980
+ setState((prev) => ({
17981
+ ...prev,
17982
+ loading: false,
17983
+ permission: isPushSupported() ? Notification.permission : "unsupported",
17984
+ error: error instanceof Error ? error.message : "Could not enable push notifications"
17985
+ }));
17986
+ return false;
17987
+ }
17988
+ }, [refresh]);
17989
+ const disable = React2.useCallback(async () => {
17990
+ setState((prev) => ({ ...prev, loading: true, error: null }));
17991
+ try {
17992
+ await disablePushOnThisDevice();
17993
+ } finally {
17994
+ await refresh();
17995
+ }
17996
+ }, [refresh]);
17997
+ const setUserEnabled = React2.useCallback(
17998
+ async (enabled) => {
17999
+ setState((prev) => ({ ...prev, userEnabled: enabled }));
18000
+ try {
18001
+ await apiUpdatePushPreference(enabled);
18002
+ } catch (error) {
18003
+ setState((prev) => ({
18004
+ ...prev,
18005
+ userEnabled: !enabled,
18006
+ error: error instanceof Error ? error.message : "Could not update push preference"
18007
+ }));
18008
+ }
18009
+ },
18010
+ []
18011
+ );
18012
+ return { ...state, enable, disable, setUserEnabled, refresh };
18013
+ };
18014
+ var PushNotificationToggle = ({
18015
+ className,
18016
+ label = "Push notifications",
18017
+ description = "Get notified about new messages on this device"
18018
+ }) => {
18019
+ const push = usePushNotifications();
18020
+ if (!push.supported || !push.allowedByWorkspace || !push.configured) {
18021
+ return null;
18022
+ }
18023
+ const isOn = push.deviceEnabled && push.userEnabled;
18024
+ const permissionBlocked = push.permission === "denied";
18025
+ const handleChange = async (checked) => {
18026
+ if (checked) {
18027
+ if (!push.userEnabled) await push.setUserEnabled(true);
18028
+ if (!push.deviceEnabled) await push.enable();
18029
+ } else {
18030
+ await push.disable();
18031
+ }
18032
+ };
18033
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18034
+ "div",
18035
+ {
18036
+ className: className ?? "flex items-center justify-between gap-4 rounded-xl border border-border bg-background px-4 py-3",
18037
+ children: [
18038
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
18039
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: label }),
18040
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description })
18041
+ ] }),
18042
+ /* @__PURE__ */ jsxRuntime.jsx(
18043
+ Switch,
18044
+ {
18045
+ checked: isOn,
18046
+ disabled: push.loading || permissionBlocked,
18047
+ onCheckedChange: (checked) => void handleChange(checked),
18048
+ "aria-label": `Toggle ${label}`
18049
+ }
18050
+ )
18051
+ ]
18052
+ }
18053
+ );
18054
+ };
18055
+
17780
18056
  // src/index.ts
17781
18057
  var index_default = ChatMain_default;
17782
18058
 
@@ -17805,6 +18081,12 @@ exports.LoggedUserDetails = LoggedUserDetails_default;
17805
18081
  exports.MessageInput = ChannelMessageBoxView_default;
17806
18082
  exports.MessageItem = MessageItemView;
17807
18083
  exports.MessageList = ActiveChannelMessagesView_default;
18084
+ exports.PushNotificationToggle = PushNotificationToggle;
18085
+ exports.apiGetPushConfig = apiGetPushConfig;
18086
+ exports.apiGetPushPreference = apiGetPushPreference;
18087
+ exports.apiRegisterPushToken = apiRegisterPushToken;
18088
+ exports.apiRemovePushToken = apiRemovePushToken;
18089
+ exports.apiUpdatePushPreference = apiUpdatePushPreference;
17808
18090
  exports.capitalizeFirst = capitalizeFirst;
17809
18091
  exports.clampJumpDate = clampJumpDate;
17810
18092
  exports.default = index_default;
@@ -17812,13 +18094,18 @@ exports.defaultChatClassNames = defaultChatClassNames;
17812
18094
  exports.defaultChatComponents = defaultChatComponents;
17813
18095
  exports.defaultChatFeatures = defaultChatFeatures;
17814
18096
  exports.defaultChatLayoutConfig = defaultChatLayoutConfig;
18097
+ exports.disablePushOnThisDevice = disablePushOnThisDevice;
17815
18098
  exports.downloadFile = downloadFile;
18099
+ exports.enablePushOnThisDevice = enablePushOnThisDevice;
17816
18100
  exports.getFirstAndLastNameOneCharacter = getFirstAndLastNameOneCharacter;
17817
18101
  exports.getResponsiveWidth = getResponsiveWidth;
17818
18102
  exports.getSocketConfig = getSocketConfig;
18103
+ exports.getStoredPushToken = getStoredPushToken;
17819
18104
  exports.isEmpty = isEmpty;
18105
+ exports.isPushSupported = isPushSupported;
17820
18106
  exports.mergeChatCustomization = mergeChatCustomization;
17821
18107
  exports.mergeChatLayoutConfig = mergeChatLayoutConfig;
18108
+ exports.onForegroundPush = onForegroundPush;
17822
18109
  exports.packageDefaultComponents = packageDefaultComponents;
17823
18110
  exports.resolveApiUrl = resolveApiUrl;
17824
18111
  exports.resolveChatApiKey = resolveChatApiKey;
@@ -17840,5 +18127,6 @@ exports.useChatFeatures = useChatFeatures;
17840
18127
  exports.useChatFeaturesOptional = useChatFeaturesOptional;
17841
18128
  exports.useChatLocale = useChatLocale;
17842
18129
  exports.useChatTheme = useChatTheme;
18130
+ exports.usePushNotifications = usePushNotifications;
17843
18131
  //# sourceMappingURL=index.cjs.map
17844
18132
  //# sourceMappingURL=index.cjs.map