@rivium/push-web 0.1.0 → 0.1.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/dist/index.d.ts +15 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -329,6 +329,8 @@ export type RiviumPushAnalyticsCallback = (event: RiviumPushAnalyticsEvent, prop
|
|
|
329
329
|
declare class RiviumPush {
|
|
330
330
|
private config;
|
|
331
331
|
private deviceId;
|
|
332
|
+
private subscriptionId;
|
|
333
|
+
private userId;
|
|
332
334
|
private pnSocket;
|
|
333
335
|
private serviceWorkerRegistration;
|
|
334
336
|
private pushSubscription;
|
|
@@ -429,13 +431,24 @@ declare class RiviumPush {
|
|
|
429
431
|
*/
|
|
430
432
|
getDeviceId(): string | null;
|
|
431
433
|
/**
|
|
432
|
-
*
|
|
434
|
+
* Get the per-install subscription ID issued by the server during registration.
|
|
435
|
+
* This is the canonical addressing key for inbox / A-B / in-app calls and the
|
|
436
|
+
* new MQTT topic. Returns `null` until registration succeeds at least once.
|
|
437
|
+
*/
|
|
438
|
+
getSubscriptionId(): string | null;
|
|
439
|
+
/**
|
|
440
|
+
* Set user ID. Persisted in localStorage so subsequent page loads pick up
|
|
441
|
+
* the same identity automatically (matches OneSignal/Airship behaviour).
|
|
433
442
|
*/
|
|
434
443
|
setUserId(userId: string): Promise<void>;
|
|
435
444
|
/**
|
|
436
|
-
* Clear user ID
|
|
445
|
+
* Clear user ID. Call this on logout.
|
|
437
446
|
*/
|
|
438
447
|
clearUserId(): void;
|
|
448
|
+
/**
|
|
449
|
+
* Get the currently-stored userId, if any. Survives page reloads.
|
|
450
|
+
*/
|
|
451
|
+
getUserId(): string | null;
|
|
439
452
|
/**
|
|
440
453
|
* Get the message that launched/opened the app (when user tapped a notification)
|
|
441
454
|
* Returns null if the app was not opened from a notification tap
|
package/dist/index.esm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{PNConfigBuilder as e,PNAuthFactory as t,PNSocket as i}from"@rivium/pn-protocol";var n;!function(e){e[e.CONNECTION_FAILED=1e3]="CONNECTION_FAILED",e[e.CONNECTION_TIMEOUT=1001]="CONNECTION_TIMEOUT",e[e.CONNECTION_LOST=1002]="CONNECTION_LOST",e[e.CONNECTION_REFUSED=1003]="CONNECTION_REFUSED",e[e.AUTHENTICATION_FAILED=1004]="AUTHENTICATION_FAILED",e[e.SSL_ERROR=1005]="SSL_ERROR",e[e.BROKER_UNAVAILABLE=1006]="BROKER_UNAVAILABLE",e[e.SUBSCRIPTION_FAILED=1100]="SUBSCRIPTION_FAILED",e[e.UNSUBSCRIPTION_FAILED=1101]="UNSUBSCRIPTION_FAILED",e[e.INVALID_TOPIC=1102]="INVALID_TOPIC",e[e.MESSAGE_DELIVERY_FAILED=1200]="MESSAGE_DELIVERY_FAILED",e[e.MESSAGE_PARSE_ERROR=1201]="MESSAGE_PARSE_ERROR",e[e.MESSAGE_TIMEOUT=1202]="MESSAGE_TIMEOUT",e[e.INVALID_CONFIG=1300]="INVALID_CONFIG",e[e.MISSING_API_KEY=1301]="MISSING_API_KEY",e[e.MISSING_SERVER_URL=1302]="MISSING_SERVER_URL",e[e.INVALID_CREDENTIALS=1303]="INVALID_CREDENTIALS",e[e.REGISTRATION_FAILED=1400]="REGISTRATION_FAILED",e[e.DEVICE_ID_GENERATION_FAILED=1401]="DEVICE_ID_GENERATION_FAILED",e[e.SERVER_ERROR=1402]="SERVER_ERROR",e[e.NETWORK_ERROR=1403]="NETWORK_ERROR",e[e.NOT_INITIALIZED=1500]="NOT_INITIALIZED",e[e.NOT_CONNECTED=1501]="NOT_CONNECTED",e[e.ALREADY_CONNECTED=1502]="ALREADY_CONNECTED",e[e.SERVICE_NOT_RUNNING=1503]="SERVICE_NOT_RUNNING",e[e.PERMISSION_DENIED=1600]="PERMISSION_DENIED",e[e.PERMISSION_DISMISSED=1601]="PERMISSION_DISMISSED",e[e.UNKNOWN_ERROR=9999]="UNKNOWN_ERROR"}(n||(n={}));const s={[n.CONNECTION_FAILED]:"Failed to connect to MQTT broker",[n.CONNECTION_TIMEOUT]:"Connection timed out",[n.CONNECTION_LOST]:"Connection to server was lost",[n.CONNECTION_REFUSED]:"Connection was refused by server",[n.AUTHENTICATION_FAILED]:"Authentication failed - invalid credentials",[n.SSL_ERROR]:"SSL/TLS handshake failed",[n.BROKER_UNAVAILABLE]:"MQTT broker is unavailable",[n.SUBSCRIPTION_FAILED]:"Failed to subscribe to topic",[n.UNSUBSCRIPTION_FAILED]:"Failed to unsubscribe from topic",[n.INVALID_TOPIC]:"Invalid topic format",[n.MESSAGE_DELIVERY_FAILED]:"Failed to deliver message",[n.MESSAGE_PARSE_ERROR]:"Failed to parse message payload",[n.MESSAGE_TIMEOUT]:"Message delivery timed out",[n.INVALID_CONFIG]:"Invalid configuration",[n.MISSING_API_KEY]:"API key is missing",[n.MISSING_SERVER_URL]:"Server URL is missing",[n.INVALID_CREDENTIALS]:"Invalid MQTT credentials",[n.REGISTRATION_FAILED]:"Device registration failed",[n.DEVICE_ID_GENERATION_FAILED]:"Failed to generate device ID",[n.SERVER_ERROR]:"Server returned an error",[n.NETWORK_ERROR]:"Network request failed",[n.NOT_INITIALIZED]:"SDK is not initialized",[n.NOT_CONNECTED]:"Not connected to server",[n.ALREADY_CONNECTED]:"Already connected to server",[n.SERVICE_NOT_RUNNING]:"Service worker is not running",[n.PERMISSION_DENIED]:"Notification permission denied",[n.PERMISSION_DISMISSED]:"Notification permission dismissed",[n.UNKNOWN_ERROR]:"An unknown error occurred"};class o extends Error{constructor(e,t){super(s[e]||"Unknown error"),this.name="RiviumPushError",this.code=e,this.details=t}toJSON(){return{code:this.code,message:this.message,details:this.details}}}var a,r,c;!function(e){e.SDK_INITIALIZED="sdkInitialized",e.DEVICE_REGISTERED="deviceRegistered",e.DEVICE_UNREGISTERED="deviceUnregistered",e.MESSAGE_RECEIVED="messageReceived",e.MESSAGE_DISPLAYED="messageDisplayed",e.NOTIFICATION_CLICKED="notificationClicked",e.ACTION_CLICKED="actionClicked",e.CONNECTED="connected",e.DISCONNECTED="disconnected",e.CONNECTION_ERROR="connectionError",e.RETRY_STARTED="retryStarted",e.TOPIC_SUBSCRIBED="topicSubscribed",e.TOPIC_UNSUBSCRIBED="topicUnsubscribed",e.NETWORK_STATE_CHANGED="networkStateChanged",e.APP_STATE_CHANGED="appStateChanged",e.PERMISSION_REQUESTED="permissionRequested",e.PERMISSION_GRANTED="permissionGranted",e.PERMISSION_DENIED="permissionDenied"}(a||(a={})),function(e){e[e.NONE=0]="NONE",e[e.ERROR=1]="ERROR",e[e.WARNING=2]="WARNING",e[e.INFO=3]="INFO",e[e.DEBUG=4]="DEBUG",e[e.VERBOSE=5]="VERBOSE"}(r||(r={})),function(e){e.WIFI="wifi",e.CELLULAR="cellular",e.ETHERNET="ethernet",e.NONE="none",e.UNKNOWN="unknown"}(c||(c={}));const l="https://push-api.rivium.co";class h{constructor(e){var t;if(this.deviceId=null,this.pnSocket=null,this.serviceWorkerRegistration=null,this.pushSubscription=null,this.connectionState="disconnected",this.reconnectAttempts=0,this.maxReconnectAttempts=10,this.reconnectTimer=null,this.subscribedTopics=new Set,this.badgeCount=0,this.initialized=!1,this.initialMessage=null,this.mqttConfig=null,this.mqttConfigFetched=!1,this.appId=null,this.appIdentifier=null,this.vapidPublicKey=null,this.logLevel=r.DEBUG,this.analyticsCallback=null,this.analyticsEnabled=!1,this.onMessageCallback=null,this.onConnectionStateCallback=null,this.onRegisteredCallback=null,this.onErrorCallback=null,this.onDetailedErrorCallback=null,this.onNotificationClickCallback=null,this.onActionClickedCallback=null,this.onReconnectingCallback=null,this.onNetworkStateCallback=null,this.onAppStateCallback=null,!e.apiKey)throw new Error("RiviumPush: apiKey is required");this.config={serviceWorkerPath:"/rivium-push-sw.js",autoRegisterServiceWorker:!0,mqttQos:1,maxReconnectAttempts:10,logLevel:r.ERROR,...e},this.maxReconnectAttempts=this.config.maxReconnectAttempts,this.logLevel=this.config.logLevel,"undefined"!=typeof window?(this.deviceId=this.getOrCreateDeviceId(),this.badgeCount=parseInt(localStorage.getItem("rivium_push_badge_count")||"0",10),this.checkInitialMessage(),"serviceWorker"in navigator&&navigator.serviceWorker.addEventListener("message",this.handleServiceWorkerMessage.bind(this)),document.addEventListener("visibilitychange",this.handleVisibilityChange.bind(this)),"connection"in navigator&&(null===(t=navigator.connection)||void 0===t||t.addEventListener("change",this.handleNetworkChange.bind(this))),window.addEventListener("online",this.handleOnline.bind(this)),window.addEventListener("offline",this.handleOffline.bind(this)),this.initialized=!0,this.trackEvent(a.SDK_INITIALIZED),this.log(r.INFO,"RiviumPush SDK initialized"),this.fetchMqttConfig()):this.initialized=!0}async fetchMqttConfig(){try{this.log(r.DEBUG,"Fetching config from server...");const e=await fetch(`${l}/devices/config`,{method:"GET",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=await e.json();this.mqttConfig=t.mqtt,this.mqttConfigFetched=!0,t.vapidPublicKey&&(this.vapidPublicKey=t.vapidPublicKey,this.log(r.DEBUG,"VAPID public key received from server")),this.log(r.INFO,`Config fetched successfully, vapid=${!!this.vapidPublicKey}`)}catch(e){this.log(r.ERROR,"Failed to fetch config:",e),this.emitError(n.INVALID_CONFIG,`Failed to fetch config: ${e.message}`)}}log(e,t,...i){if(e>this.logLevel)return;const n="[RiviumPush]";switch(e){case r.ERROR:console.error(n,t,...i);break;case r.WARNING:console.warn(n,t,...i);break;case r.INFO:console.info(n,t,...i);break;case r.DEBUG:case r.VERBOSE:console.log(n,t,...i)}}setLogLevel(e){this.logLevel=e,this.log(r.INFO,`Log level set to ${r[e]}`)}getLogLevel(){return this.logLevel}trackEvent(e,t){if(this.analyticsEnabled&&this.analyticsCallback)try{this.analyticsCallback(e,t)}catch(e){this.log(r.ERROR,"Analytics callback error:",e)}this.log(r.VERBOSE,`Analytics event: ${e}`,t)}setAnalyticsHandler(e){this.analyticsCallback=e,this.analyticsEnabled=!0,this.log(r.INFO,"Analytics handler set")}disableAnalytics(){this.analyticsCallback=null,this.analyticsEnabled=!1,this.log(r.INFO,"Analytics disabled")}isAnalyticsEnabled(){return this.analyticsEnabled}emitError(e,t){const i=new o(e,t);this.log(r.ERROR,`Error [${e}]: ${i.message}`,t),this.onDetailedErrorCallback&&this.onDetailedErrorCallback(i),this.onErrorCallback&&this.onErrorCallback(i),this.trackEvent(a.CONNECTION_ERROR,{errorCode:e,errorMessage:i.message,details:t})}async register(e){try{this.mqttConfigFetched||(this.log(r.DEBUG,"Waiting for server config..."),await this.waitForConfig()),this.config.autoRegisterServiceWorker&&await this.registerServiceWorker(),this.trackEvent(a.PERMISSION_REQUESTED);const t=await this.requestNotificationPermission();if("denied"===t)throw this.trackEvent(a.PERMISSION_DENIED),this.emitError(n.PERMISSION_DENIED),new o(n.PERMISSION_DENIED);if("default"===t)throw this.trackEvent(a.PERMISSION_DENIED),this.emitError(n.PERMISSION_DISMISSED,"User dismissed the permission prompt"),new o(n.PERMISSION_DISMISSED);this.trackEvent(a.PERMISSION_GRANTED);const i=this.vapidPublicKey||this.config.vapidPublicKey;if(i&&this.serviceWorkerRegistration)try{this.pushSubscription=await this.subscribeToPush(i),this.log(r.INFO,"Web Push subscription created for background notifications")}catch(e){this.log(r.WARNING,"Web Push subscription failed (will use MQTT only):",e)}const s=await this.registerDevice(e);return this.deviceId=s.deviceId,this.connectToGateway(),this.onRegisteredCallback&&this.onRegisteredCallback(s.deviceId),this.trackEvent(a.DEVICE_REGISTERED,{deviceId:s.deviceId,userId:null==e?void 0:e.userId,hasWebPush:!!this.pushSubscription}),this.log(r.INFO,"Registered with device ID:",s.deviceId,"Web Push:",!!this.pushSubscription),s.deviceId}catch(e){if(e instanceof o)throw e;throw this.log(r.ERROR,"Registration failed:",e),this.emitError(n.REGISTRATION_FAILED,e.message),e}}async waitForConfig(e=5e3){const t=Date.now();for(;!this.mqttConfigFetched&&Date.now()-t<e;)await new Promise(e=>setTimeout(e,100));this.mqttConfigFetched||this.log(r.WARNING,"Config fetch timed out, continuing without it")}async unregister(){this.disconnectFromGateway(),this.pushSubscription&&(await this.pushSubscription.unsubscribe(),this.pushSubscription=null),this.trackEvent(a.DEVICE_UNREGISTERED,{deviceId:this.deviceId}),this.log(r.INFO,"Unregistered")}async subscribeTopic(e){if(e&&""!==e.trim()){if(this.subscribedTopics.add(e),this.deviceId)try{await fetch(`${l}/topics/subscribe`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify({deviceId:this.deviceId,topic:e})})}catch(e){this.log(r.WARNING,"Failed to register topic on server:",e)}if(this.pnSocket&&this.pnSocket.isConnected()){const t=`rivium_push/${this.config.apiKey.substring(0,16)}/topic/${e}`;this.pnSocket.stream(t,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.INFO,"Subscribed to topic:",e),this.trackEvent(a.TOPIC_SUBSCRIBED,{topic:e})}}else this.emitError(n.INVALID_TOPIC,"Topic cannot be empty")}async unsubscribeTopic(e){if(this.subscribedTopics.delete(e),this.deviceId)try{await fetch(`${l}/topics/unsubscribe`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify({deviceId:this.deviceId,topic:e})})}catch(e){this.log(r.WARNING,"Failed to unregister topic on server:",e)}if(this.pnSocket&&this.pnSocket.isConnected()){const t=`rivium_push/${this.config.apiKey.substring(0,16)}/topic/${e}`;this.pnSocket.detach(t),this.log(r.INFO,"Unsubscribed from topic:",e),this.trackEvent(a.TOPIC_UNSUBSCRIBED,{topic:e})}}isConnected(){return"connected"===this.connectionState}getDeviceId(){return this.deviceId}async setUserId(e){localStorage.setItem("rivium_push_user_id",e),await this.registerDevice({userId:e}),this.log(r.INFO,"User ID set:",e)}clearUserId(){localStorage.removeItem("rivium_push_user_id"),this.log(r.INFO,"User ID cleared")}getInitialMessage(){return this.initialMessage}getBadgeCount(){return this.badgeCount}setBadgeCount(e){this.badgeCount=Math.max(0,e),localStorage.setItem("rivium_push_badge_count",this.badgeCount.toString()),this.updateFaviconBadge(this.badgeCount),"setAppBadge"in navigator&&(this.badgeCount>0?navigator.setAppBadge(this.badgeCount):navigator.clearAppBadge())}clearBadge(){this.setBadgeCount(0)}async refreshMqttToken(){if(!this.deviceId)throw new o(n.NOT_INITIALIZED,"Device not registered");try{const e=await fetch(`${l}/devices/${this.deviceId}/mqtt-token/refresh`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok){const t=await e.json().catch(()=>({}));throw new o(n.SERVER_ERROR,t.message||`HTTP ${e.status}`)}const t=await e.json();t.token&&this.mqttConfig&&(this.mqttConfig.token=t.token,this.log(r.INFO,"MQTT token refreshed successfully"))}catch(e){if(e instanceof o)throw e;throw new o(n.NETWORK_ERROR,e.message)}}getNetworkState(){const e=navigator.connection;return{isAvailable:navigator.onLine,networkType:this.detectNetworkType(),effectiveType:null==e?void 0:e.effectiveType,downlink:null==e?void 0:e.downlink,rtt:null==e?void 0:e.rtt}}getAppState(){return{isVisible:"visible"===document.visibilityState,visibilityState:document.visibilityState}}static isSupported(){return"undefined"!=typeof window&&"Notification"in window&&"serviceWorker"in navigator}static getPermissionStatus(){return"undefined"==typeof Notification?"denied":Notification.permission}onMessage(e){return this.onMessageCallback=e,()=>{this.onMessageCallback=null}}onConnectionState(e){return this.onConnectionStateCallback=e,()=>{this.onConnectionStateCallback=null}}onRegistered(e){return this.onRegisteredCallback=e,()=>{this.onRegisteredCallback=null}}onError(e){return this.onErrorCallback=e,()=>{this.onErrorCallback=null}}onDetailedError(e){return this.onDetailedErrorCallback=e,()=>{this.onDetailedErrorCallback=null}}onNotificationClick(e){return this.onNotificationClickCallback=e,()=>{this.onNotificationClickCallback=null}}onActionClicked(e){return this.onActionClickedCallback=e,()=>{this.onActionClickedCallback=null}}onReconnecting(e){return this.onReconnectingCallback=e,()=>{this.onReconnectingCallback=null}}onNetworkState(e){return this.onNetworkStateCallback=e,()=>{this.onNetworkStateCallback=null}}onAppState(e){return this.onAppStateCallback=e,()=>{this.onAppStateCallback=null}}detectNetworkType(){const e=navigator.connection;if(!e)return c.UNKNOWN;switch(e.type){case"wifi":return c.WIFI;case"cellular":return c.CELLULAR;case"ethernet":return c.ETHERNET;case"none":return c.NONE;default:return c.UNKNOWN}}handleNetworkChange(){const e=this.getNetworkState();this.log(r.DEBUG,"Network state changed:",e),this.onNetworkStateCallback&&this.onNetworkStateCallback(e),this.trackEvent(a.NETWORK_STATE_CHANGED,{isAvailable:e.isAvailable,networkType:e.networkType,effectiveType:e.effectiveType})}handleOnline(){this.log(r.INFO,"Network online"),this.handleNetworkChange(),"disconnected"===this.connectionState&&this.deviceId&&(this.log(r.INFO,"Reconnecting after network restored"),this.connectToGateway())}handleOffline(){this.log(r.INFO,"Network offline"),this.handleNetworkChange()}handleVisibilityChange(){const e=this.getAppState();this.log(r.DEBUG,"App state changed:",e),this.onAppStateCallback&&this.onAppStateCallback(e),this.trackEvent(a.APP_STATE_CHANGED,{isVisible:e.isVisible,visibilityState:e.visibilityState}),e.isVisible&&"disconnected"===this.connectionState&&this.deviceId&&(this.log(r.INFO,"Reconnecting after becoming visible"),this.connectToGateway())}checkInitialMessage(){if("undefined"!=typeof window){const e=new URLSearchParams(window.location.search).get("rivium_push_notification");if(e)try{this.initialMessage=JSON.parse(decodeURIComponent(e)),this.log(r.INFO,"Initial message found:",this.initialMessage)}catch(e){this.log(r.WARNING,"Failed to parse initial message:",e)}const t=sessionStorage.getItem("rivium_push_initial_message");if(t&&!this.initialMessage)try{this.initialMessage=JSON.parse(t),sessionStorage.removeItem("rivium_push_initial_message"),this.log(r.INFO,"Initial message from session:",this.initialMessage)}catch(e){this.log(r.WARNING,"Failed to parse stored message:",e)}}}async registerServiceWorker(){if(!("serviceWorker"in navigator))throw new o(n.SERVICE_NOT_RUNNING,"Service Workers not supported");try{this.serviceWorkerRegistration=await navigator.serviceWorker.register(this.config.serviceWorkerPath,{scope:"/"}),this.log(r.INFO,"Service Worker registered")}catch(e){throw this.log(r.ERROR,"Service Worker registration failed:",e),new o(n.SERVICE_NOT_RUNNING,e.message)}}async requestNotificationPermission(){return"undefined"==typeof Notification?"denied":"granted"===Notification.permission?"granted":await Notification.requestPermission()}async subscribeToPush(e){if(!this.serviceWorkerRegistration)throw new o(n.SERVICE_NOT_RUNNING,"Service Worker not registered");const t=await this.serviceWorkerRegistration.pushManager.getSubscription();if(t)return this.log(r.DEBUG,"Using existing Push subscription"),t;const i=await this.serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:this.urlBase64ToUint8Array(e)});return this.log(r.INFO,"Push subscription created"),i}async registerDevice(e){var t,i,s;try{const a={deviceId:this.deviceId,platform:"web",userId:null==e?void 0:e.userId,appIdentifier:"undefined"!=typeof window?window.location.origin:void 0,metadata:{...null==e?void 0:e.metadata,userAgent:navigator.userAgent,language:navigator.language,url:window.location.origin}};if(this.pushSubscription){const e=this.pushSubscription.toJSON();a.webPushSubscription={endpoint:e.endpoint,keys:{p256dh:(null===(t=e.keys)||void 0===t?void 0:t.p256dh)||"",auth:(null===(i=e.keys)||void 0===i?void 0:i.auth)||""}},this.log(r.DEBUG,"Sending Web Push subscription to server")}const c=await fetch(`${l}/devices/register`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify(a)});if(!c.ok){const e=await c.json().catch(()=>({}));throw new o(n.SERVER_ERROR,e.message||`HTTP ${c.status}`)}const h=await c.json();return h.appId&&(this.appId=h.appId),h.appIdentifier&&(this.appIdentifier=h.appIdentifier),(null===(s=h.mqtt)||void 0===s?void 0:s.token)&&this.mqttConfig&&(this.mqttConfig.token=h.mqtt.token,this.log(r.DEBUG,"Connection token received from registration")),h}catch(e){if(e instanceof o)throw e;throw new o(n.NETWORK_ERROR,e.message)}}connectToGateway(){if(this.pnSocket&&this.pnSocket.close(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),!this.mqttConfig)return this.log(r.WARNING,"Gateway config not available, retrying in 2s..."),void setTimeout(()=>this.connectToGateway(),2e3);if(!this.mqttConfig.token)return this.log(r.ERROR,"Connection token not available. Device must be registered first."),void this.emitError(n.AUTHENTICATION_FAILED,"Connection token not available");this.setConnectionState("connecting");const s=this.mqttConfig.wsHost||this.mqttConfig.host,o=this.mqttConfig.wsPort,c="undefined"!=typeof window&&"https:"===window.location.protocol||443===o;this.log(r.DEBUG,`Connecting to gateway (secure: ${c})`);const l=`rivium_push_${this.appId}_${this.deviceId}`,h=(new e).gateway(s).port(o).clientId(l).auth(t.token(this.mqttConfig.token)).secure(c).freshStart(!1).autoReconnect(!1).connectionTimeout(10).build();this.pnSocket=new i(h);const d={onStateChanged:e=>{this.log(r.DEBUG,`Connection state changed: ${e}`)},onConnected:()=>{this.log(r.INFO,"Connected to gateway"),this.setConnectionState("connected"),this.reconnectAttempts=0,this.trackEvent(a.CONNECTED);const e=this.config.apiKey.substring(0,16),t=this.appIdentifier||("undefined"!=typeof window?window.location.origin:"_default"),i=`rivium_push/${e}/${this.deviceId}/${t}`;this.pnSocket.stream(i,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.DEBUG,"Streaming from device channel");const n=`rivium_push/${e}/broadcast`;this.pnSocket.stream(n,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.DEBUG,"Streaming from broadcast channel"),this.subscribedTopics.forEach(t=>{const i=`rivium_push/${e}/topic/${t}`;this.pnSocket.stream(i,e=>{this.handlePNMessage(e)},this.config.mqttQos)})},onDisconnected:e=>{this.log(r.INFO,"Disconnected from gateway",e||""),this.setConnectionState("disconnected"),this.trackEvent(a.DISCONNECTED),this.scheduleReconnect()},onReconnecting:(e,t)=>{this.log(r.INFO,`Reconnecting attempt ${e} in ${t}ms`)}};this.pnSocket.addConnectionListener(d),this.pnSocket.addErrorListener(e=>{this.log(r.ERROR,"Gateway error:",e.message),this.setConnectionState("error");let t=n.CONNECTION_FAILED;const i=e.message.toLowerCase();if(i.includes("timeout"))t=n.CONNECTION_TIMEOUT;else if(i.includes("refused")||i.includes("not authorized")){if(t=n.CONNECTION_REFUSED,i.includes("not authorized"))return this.log(r.INFO,"Token may be expired, attempting refresh..."),void this.refreshMqttToken().then(()=>{this.log(r.INFO,"Token refreshed, reconnecting..."),this.connectToGateway()}).catch(e=>{this.log(r.ERROR,"Token refresh failed:",e),this.emitError(n.AUTHENTICATION_FAILED,"Token expired and refresh failed")})}else i.includes("auth")||i.includes("credential")?t=n.AUTHENTICATION_FAILED:(i.includes("ssl")||i.includes("tls"))&&(t=n.SSL_ERROR);this.emitError(t,e.message)}),this.log(r.DEBUG,"Opening connection to gateway..."),this.pnSocket.open()}handlePNMessage(e){try{const t=e.payloadAsJson();this.handleMqttMessage(e.channel,t)}catch(e){this.log(r.ERROR,"Message parse error:",e),this.emitError(n.MESSAGE_PARSE_ERROR,e.message)}}disconnectFromGateway(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pnSocket&&(this.pnSocket.close(),this.pnSocket=null),this.setConnectionState("disconnected")}scheduleReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return this.log(r.WARNING,"Max reconnect attempts reached"),void this.emitError(n.CONNECTION_FAILED,"Max reconnect attempts reached");if(!navigator.onLine)return void this.log(r.DEBUG,"Offline, skipping reconnect");const e=Math.min(1e3*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.log(r.INFO,`Reconnecting in ${e}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);const t={retryAttempt:this.reconnectAttempts,nextRetryMs:e,maxRetryAttempts:this.maxReconnectAttempts};this.onReconnectingCallback&&this.onReconnectingCallback(t),this.trackEvent(a.RETRY_STARTED,{retryAttempt:this.reconnectAttempts,nextRetryMs:e}),this.reconnectTimer=setTimeout(()=>{this.connectToGateway()},e)}handleMqttMessage(e,t){var i;const n=this.normalizeMessage(t);this.log(r.DEBUG,"Message received:",n.title),this.trackEvent(a.MESSAGE_RECEIVED,{messageId:n.messageId,title:n.title,silent:n.silent,hasImage:!!n.imageUrl,hasActions:!!(null===(i=n.actions)||void 0===i?void 0:i.length)}),this.handleBadge(n),n.silent||"visible"===document.visibilityState||(this.showRichNotification(n),this.trackEvent(a.MESSAGE_DISPLAYED,{messageId:n.messageId,title:n.title})),this.onMessageCallback&&this.onMessageCallback(n)}normalizeMessage(e){const t=this.getLocalizedContent(e,"title"),i=this.getLocalizedContent(e,"body");return{title:t||e.title||"",body:i||e.body||"",data:e.data,silent:e.silent,imageUrl:e.imageUrl||e.image,iconUrl:e.iconUrl||e.icon,actions:e.actions,deepLink:e.deepLink,badge:e.badge,badgeAction:e.badgeAction,sound:e.sound,threadId:e.threadId,collapseKey:e.collapseKey,category:e.category,priority:e.priority,ttl:e.ttl,localizations:e.localizations,timezone:e.timezone,messageId:e.messageId,campaignId:e.campaignId,icon:e.iconUrl||e.icon,image:e.imageUrl||e.image,tag:e.tag||e.collapseKey||e.threadId}}getLocalizedContent(e,t){if(!e.localizations||!Array.isArray(e.localizations))return null;const i=navigator.language.split("-")[0].toLowerCase(),n=e.localizations.find(e=>e.locale.toLowerCase().startsWith(i));return n?n[t]:null}handleBadge(e){if(void 0===e.badge&&!e.badgeAction)return;const t=e.badgeAction||"set";let i=e.badge||0;switch(t){case"set":i=e.badge||0;break;case"increment":i=this.badgeCount+(e.badge||1);break;case"decrement":i=Math.max(0,this.badgeCount-(e.badge||1));break;case"clear":i=0}this.setBadgeCount(i)}handleServiceWorkerMessage(e){var t;const i=e.data;if("push-message"===i.type){this.log(r.DEBUG,"Push message received from SW:",null===(t=i.message)||void 0===t?void 0:t.title);const e=this.normalizeMessage(i.message);this.trackEvent(a.MESSAGE_RECEIVED,{messageId:e.messageId,title:e.title,source:"web-push"}),this.handleBadge(e),this.onMessageCallback&&this.onMessageCallback(e)}else if("notification-click"===i.type){this.log(r.INFO,"Notification clicked from SW");const e=this.normalizeMessage(i.message||{});this.trackEvent(a.NOTIFICATION_CLICKED,{messageId:e.messageId,title:e.title,action:i.action}),this.onNotificationClickCallback&&this.onNotificationClickCallback(e,i.action)}else if("action-clicked"===i.type){this.log(r.INFO,"Action clicked from SW:",i.actionId);const e=this.normalizeMessage(i.message||{});this.trackEvent(a.ACTION_CLICKED,{actionId:i.actionId,messageId:e.messageId,title:e.title}),this.onActionClickedCallback&&this.onActionClickedCallback(i.actionId,e)}else"initial-message"===i.type?(this.log(r.INFO,"Initial message received from SW"),this.initialMessage=this.normalizeMessage(i.message||{}),this.onNotificationClickCallback&&this.initialMessage&&this.onNotificationClickCallback(this.initialMessage,void 0)):"navigate"===i.type&&(this.log(r.INFO,"Navigating to:",i.url),i.url&&"undefined"!=typeof window&&(window.location.href=i.url))}showRichNotification(e){if("granted"!==Notification.permission)return;const t={body:e.body,icon:e.iconUrl||e.icon,badge:e.iconUrl||e.icon,image:e.imageUrl||e.image,tag:e.tag||e.collapseKey||e.threadId,data:{...e.data,deepLink:e.deepLink,messageId:e.messageId,campaignId:e.campaignId,riviumPushMessage:e},requireInteraction:"high"===e.priority,silent:"none"===e.sound};if(e.actions&&e.actions.length>0){const i=e.actions.slice(0,2).map(e=>({action:e.id,title:e.title,icon:e.icon}));this.serviceWorkerRegistration&&(t.actions=i)}if(this.serviceWorkerRegistration)this.serviceWorkerRegistration.showNotification(e.title,t);else{const i=new Notification(e.title,t);i.onclick=()=>{window.focus(),e.deepLink&&(window.location.href=e.deepLink),this.onNotificationClickCallback&&this.onNotificationClickCallback(e),this.trackEvent(a.NOTIFICATION_CLICKED,{messageId:e.messageId,title:e.title}),i.close()}}}updateFaviconBadge(e){try{const t=document.createElement("canvas"),i=t.getContext("2d");if(!i)return;const n=32;t.width=n,t.height=n;const s=document.querySelector('link[rel="icon"]'),o=(null==s?void 0:s.href)||"/favicon.ico",a=new Image;a.crossOrigin="anonymous",a.onload=()=>{if(i.drawImage(a,0,0,n,n),e>0){const t=14,s=n-t/2,o=t/2;i.beginPath(),i.arc(s,o,t/2+1,0,2*Math.PI),i.fillStyle="#ef4444",i.fill(),i.fillStyle="#ffffff",i.font="bold 10px sans-serif",i.textAlign="center",i.textBaseline="middle",i.fillText(e>99?"99+":e.toString(),s,o)}const o=document.createElement("link");o.rel="icon",o.href=t.toDataURL("image/png"),s&&s.remove(),document.head.appendChild(o)},a.src=o}catch(e){this.log(r.WARNING,"Could not update favicon badge:",e)}}setConnectionState(e){this.connectionState=e,this.onConnectionStateCallback&&this.onConnectionStateCallback(e)}getOrCreateDeviceId(){const e="rivium_push_device_id";let t=localStorage.getItem(e);return t||(t="web_"+this.generateUUID(),localStorage.setItem(e,t)),t}generateUUID(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}urlBase64ToUint8Array(e){const t=(e+"=".repeat((4-e.length%4)%4)).replace(/-/g,"+").replace(/_/g,"/"),i=window.atob(t),n=new Uint8Array(i.length);for(let e=0;e<i.length;++e)n[e]=i.charCodeAt(e);return n}}export{c as NetworkType,h as RiviumPush,a as RiviumPushAnalyticsEvent,o as RiviumPushError,n as RiviumPushErrorCode,r as RiviumPushLogLevel,h as default};
|
|
1
|
+
import{PNConfigBuilder as e,PNAuthFactory as t,PNSocket as i}from"@rivium/pn-protocol";var n;!function(e){e[e.CONNECTION_FAILED=1e3]="CONNECTION_FAILED",e[e.CONNECTION_TIMEOUT=1001]="CONNECTION_TIMEOUT",e[e.CONNECTION_LOST=1002]="CONNECTION_LOST",e[e.CONNECTION_REFUSED=1003]="CONNECTION_REFUSED",e[e.AUTHENTICATION_FAILED=1004]="AUTHENTICATION_FAILED",e[e.SSL_ERROR=1005]="SSL_ERROR",e[e.BROKER_UNAVAILABLE=1006]="BROKER_UNAVAILABLE",e[e.SUBSCRIPTION_FAILED=1100]="SUBSCRIPTION_FAILED",e[e.UNSUBSCRIPTION_FAILED=1101]="UNSUBSCRIPTION_FAILED",e[e.INVALID_TOPIC=1102]="INVALID_TOPIC",e[e.MESSAGE_DELIVERY_FAILED=1200]="MESSAGE_DELIVERY_FAILED",e[e.MESSAGE_PARSE_ERROR=1201]="MESSAGE_PARSE_ERROR",e[e.MESSAGE_TIMEOUT=1202]="MESSAGE_TIMEOUT",e[e.INVALID_CONFIG=1300]="INVALID_CONFIG",e[e.MISSING_API_KEY=1301]="MISSING_API_KEY",e[e.MISSING_SERVER_URL=1302]="MISSING_SERVER_URL",e[e.INVALID_CREDENTIALS=1303]="INVALID_CREDENTIALS",e[e.REGISTRATION_FAILED=1400]="REGISTRATION_FAILED",e[e.DEVICE_ID_GENERATION_FAILED=1401]="DEVICE_ID_GENERATION_FAILED",e[e.SERVER_ERROR=1402]="SERVER_ERROR",e[e.NETWORK_ERROR=1403]="NETWORK_ERROR",e[e.NOT_INITIALIZED=1500]="NOT_INITIALIZED",e[e.NOT_CONNECTED=1501]="NOT_CONNECTED",e[e.ALREADY_CONNECTED=1502]="ALREADY_CONNECTED",e[e.SERVICE_NOT_RUNNING=1503]="SERVICE_NOT_RUNNING",e[e.PERMISSION_DENIED=1600]="PERMISSION_DENIED",e[e.PERMISSION_DISMISSED=1601]="PERMISSION_DISMISSED",e[e.UNKNOWN_ERROR=9999]="UNKNOWN_ERROR"}(n||(n={}));const s={[n.CONNECTION_FAILED]:"Failed to connect to MQTT broker",[n.CONNECTION_TIMEOUT]:"Connection timed out",[n.CONNECTION_LOST]:"Connection to server was lost",[n.CONNECTION_REFUSED]:"Connection was refused by server",[n.AUTHENTICATION_FAILED]:"Authentication failed - invalid credentials",[n.SSL_ERROR]:"SSL/TLS handshake failed",[n.BROKER_UNAVAILABLE]:"MQTT broker is unavailable",[n.SUBSCRIPTION_FAILED]:"Failed to subscribe to topic",[n.UNSUBSCRIPTION_FAILED]:"Failed to unsubscribe from topic",[n.INVALID_TOPIC]:"Invalid topic format",[n.MESSAGE_DELIVERY_FAILED]:"Failed to deliver message",[n.MESSAGE_PARSE_ERROR]:"Failed to parse message payload",[n.MESSAGE_TIMEOUT]:"Message delivery timed out",[n.INVALID_CONFIG]:"Invalid configuration",[n.MISSING_API_KEY]:"API key is missing",[n.MISSING_SERVER_URL]:"Server URL is missing",[n.INVALID_CREDENTIALS]:"Invalid MQTT credentials",[n.REGISTRATION_FAILED]:"Device registration failed",[n.DEVICE_ID_GENERATION_FAILED]:"Failed to generate device ID",[n.SERVER_ERROR]:"Server returned an error",[n.NETWORK_ERROR]:"Network request failed",[n.NOT_INITIALIZED]:"SDK is not initialized",[n.NOT_CONNECTED]:"Not connected to server",[n.ALREADY_CONNECTED]:"Already connected to server",[n.SERVICE_NOT_RUNNING]:"Service worker is not running",[n.PERMISSION_DENIED]:"Notification permission denied",[n.PERMISSION_DISMISSED]:"Notification permission dismissed",[n.UNKNOWN_ERROR]:"An unknown error occurred"};class o extends Error{constructor(e,t){super(s[e]||"Unknown error"),this.name="RiviumPushError",this.code=e,this.details=t}toJSON(){return{code:this.code,message:this.message,details:this.details}}}var a,r,c;!function(e){e.SDK_INITIALIZED="sdkInitialized",e.DEVICE_REGISTERED="deviceRegistered",e.DEVICE_UNREGISTERED="deviceUnregistered",e.MESSAGE_RECEIVED="messageReceived",e.MESSAGE_DISPLAYED="messageDisplayed",e.NOTIFICATION_CLICKED="notificationClicked",e.ACTION_CLICKED="actionClicked",e.CONNECTED="connected",e.DISCONNECTED="disconnected",e.CONNECTION_ERROR="connectionError",e.RETRY_STARTED="retryStarted",e.TOPIC_SUBSCRIBED="topicSubscribed",e.TOPIC_UNSUBSCRIBED="topicUnsubscribed",e.NETWORK_STATE_CHANGED="networkStateChanged",e.APP_STATE_CHANGED="appStateChanged",e.PERMISSION_REQUESTED="permissionRequested",e.PERMISSION_GRANTED="permissionGranted",e.PERMISSION_DENIED="permissionDenied"}(a||(a={})),function(e){e[e.NONE=0]="NONE",e[e.ERROR=1]="ERROR",e[e.WARNING=2]="WARNING",e[e.INFO=3]="INFO",e[e.DEBUG=4]="DEBUG",e[e.VERBOSE=5]="VERBOSE"}(r||(r={})),function(e){e.WIFI="wifi",e.CELLULAR="cellular",e.ETHERNET="ethernet",e.NONE="none",e.UNKNOWN="unknown"}(c||(c={}));const l="https://push-api.rivium.co";class h{constructor(e){var t;if(this.deviceId=null,this.subscriptionId=null,this.userId=null,this.pnSocket=null,this.serviceWorkerRegistration=null,this.pushSubscription=null,this.connectionState="disconnected",this.reconnectAttempts=0,this.maxReconnectAttempts=10,this.reconnectTimer=null,this.subscribedTopics=new Set,this.badgeCount=0,this.initialized=!1,this.initialMessage=null,this.mqttConfig=null,this.mqttConfigFetched=!1,this.appId=null,this.appIdentifier=null,this.vapidPublicKey=null,this.logLevel=r.DEBUG,this.analyticsCallback=null,this.analyticsEnabled=!1,this.onMessageCallback=null,this.onConnectionStateCallback=null,this.onRegisteredCallback=null,this.onErrorCallback=null,this.onDetailedErrorCallback=null,this.onNotificationClickCallback=null,this.onActionClickedCallback=null,this.onReconnectingCallback=null,this.onNetworkStateCallback=null,this.onAppStateCallback=null,!e.apiKey)throw new Error("RiviumPush: apiKey is required");this.config={serviceWorkerPath:"/rivium-push-sw.js",autoRegisterServiceWorker:!0,mqttQos:1,maxReconnectAttempts:10,logLevel:r.ERROR,...e},this.maxReconnectAttempts=this.config.maxReconnectAttempts,this.logLevel=this.config.logLevel,"undefined"!=typeof window?(this.deviceId=this.getOrCreateDeviceId(),this.subscriptionId=localStorage.getItem("rivium_push_subscription_id")||null,this.userId=localStorage.getItem("rivium_push_user_id")||null,this.badgeCount=parseInt(localStorage.getItem("rivium_push_badge_count")||"0",10),this.checkInitialMessage(),"serviceWorker"in navigator&&navigator.serviceWorker.addEventListener("message",this.handleServiceWorkerMessage.bind(this)),document.addEventListener("visibilitychange",this.handleVisibilityChange.bind(this)),"connection"in navigator&&(null===(t=navigator.connection)||void 0===t||t.addEventListener("change",this.handleNetworkChange.bind(this))),window.addEventListener("online",this.handleOnline.bind(this)),window.addEventListener("offline",this.handleOffline.bind(this)),this.initialized=!0,this.trackEvent(a.SDK_INITIALIZED),this.log(r.INFO,"RiviumPush SDK initialized"),this.fetchMqttConfig()):this.initialized=!0}async fetchMqttConfig(){try{this.log(r.DEBUG,"Fetching config from server...");const e=await fetch(`${l}/devices/config`,{method:"GET",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=await e.json();this.mqttConfig=t.mqtt,this.mqttConfigFetched=!0,t.vapidPublicKey&&(this.vapidPublicKey=t.vapidPublicKey,this.log(r.DEBUG,"VAPID public key received from server")),this.log(r.INFO,`Config fetched successfully, vapid=${!!this.vapidPublicKey}`)}catch(e){this.log(r.ERROR,"Failed to fetch config:",e),this.emitError(n.INVALID_CONFIG,`Failed to fetch config: ${e.message}`)}}log(e,t,...i){if(e>this.logLevel)return;const n="[RiviumPush]";switch(e){case r.ERROR:console.error(n,t,...i);break;case r.WARNING:console.warn(n,t,...i);break;case r.INFO:console.info(n,t,...i);break;case r.DEBUG:case r.VERBOSE:console.log(n,t,...i)}}setLogLevel(e){this.logLevel=e,this.log(r.INFO,`Log level set to ${r[e]}`)}getLogLevel(){return this.logLevel}trackEvent(e,t){if(this.analyticsEnabled&&this.analyticsCallback)try{this.analyticsCallback(e,t)}catch(e){this.log(r.ERROR,"Analytics callback error:",e)}this.log(r.VERBOSE,`Analytics event: ${e}`,t)}setAnalyticsHandler(e){this.analyticsCallback=e,this.analyticsEnabled=!0,this.log(r.INFO,"Analytics handler set")}disableAnalytics(){this.analyticsCallback=null,this.analyticsEnabled=!1,this.log(r.INFO,"Analytics disabled")}isAnalyticsEnabled(){return this.analyticsEnabled}emitError(e,t){const i=new o(e,t);this.log(r.ERROR,`Error [${e}]: ${i.message}`,t),this.onDetailedErrorCallback&&this.onDetailedErrorCallback(i),this.onErrorCallback&&this.onErrorCallback(i),this.trackEvent(a.CONNECTION_ERROR,{errorCode:e,errorMessage:i.message,details:t})}async register(e){var t,i;try{this.mqttConfigFetched||(this.log(r.DEBUG,"Waiting for server config..."),await this.waitForConfig()),this.config.autoRegisterServiceWorker&&await this.registerServiceWorker(),this.trackEvent(a.PERMISSION_REQUESTED);const s=await this.requestNotificationPermission();if("denied"===s)throw this.trackEvent(a.PERMISSION_DENIED),this.emitError(n.PERMISSION_DENIED),new o(n.PERMISSION_DENIED);if("default"===s)throw this.trackEvent(a.PERMISSION_DENIED),this.emitError(n.PERMISSION_DISMISSED,"User dismissed the permission prompt"),new o(n.PERMISSION_DISMISSED);this.trackEvent(a.PERMISSION_GRANTED);const c=this.vapidPublicKey||this.config.vapidPublicKey;if(c&&this.serviceWorkerRegistration)try{this.pushSubscription=await this.subscribeToPush(c),this.log(r.INFO,"Web Push subscription created for background notifications")}catch(e){this.log(r.WARNING,"Web Push subscription failed (will use MQTT only):",e)}const l={...e,userId:null!==(i=null!==(t=null==e?void 0:e.userId)&&void 0!==t?t:this.userId)&&void 0!==i?i:void 0},h=await this.registerDevice(l);return this.deviceId=h.deviceId,this.connectToGateway(),this.onRegisteredCallback&&this.onRegisteredCallback(h.deviceId),this.trackEvent(a.DEVICE_REGISTERED,{deviceId:h.deviceId,userId:null==e?void 0:e.userId,hasWebPush:!!this.pushSubscription}),this.log(r.INFO,"Registered with device ID:",h.deviceId,"Web Push:",!!this.pushSubscription),h.deviceId}catch(e){if(e instanceof o)throw e;throw this.log(r.ERROR,"Registration failed:",e),this.emitError(n.REGISTRATION_FAILED,e.message),e}}async waitForConfig(e=5e3){const t=Date.now();for(;!this.mqttConfigFetched&&Date.now()-t<e;)await new Promise(e=>setTimeout(e,100));this.mqttConfigFetched||this.log(r.WARNING,"Config fetch timed out, continuing without it")}async unregister(){this.disconnectFromGateway(),this.pushSubscription&&(await this.pushSubscription.unsubscribe(),this.pushSubscription=null),this.trackEvent(a.DEVICE_UNREGISTERED,{deviceId:this.deviceId}),this.log(r.INFO,"Unregistered")}async subscribeTopic(e){if(e&&""!==e.trim()){if(this.subscribedTopics.add(e),this.deviceId)try{await fetch(`${l}/topics/subscribe`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify({deviceId:this.deviceId,topic:e})})}catch(e){this.log(r.WARNING,"Failed to register topic on server:",e)}if(this.pnSocket&&this.pnSocket.isConnected()){const t=`rivium_push/${this.config.apiKey.substring(0,16)}/topic/${e}`;this.pnSocket.stream(t,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.INFO,"Subscribed to topic:",e),this.trackEvent(a.TOPIC_SUBSCRIBED,{topic:e})}}else this.emitError(n.INVALID_TOPIC,"Topic cannot be empty")}async unsubscribeTopic(e){if(this.subscribedTopics.delete(e),this.deviceId)try{await fetch(`${l}/topics/unsubscribe`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify({deviceId:this.deviceId,topic:e})})}catch(e){this.log(r.WARNING,"Failed to unregister topic on server:",e)}if(this.pnSocket&&this.pnSocket.isConnected()){const t=`rivium_push/${this.config.apiKey.substring(0,16)}/topic/${e}`;this.pnSocket.detach(t),this.log(r.INFO,"Unsubscribed from topic:",e),this.trackEvent(a.TOPIC_UNSUBSCRIBED,{topic:e})}}isConnected(){return"connected"===this.connectionState}getDeviceId(){return this.deviceId}getSubscriptionId(){return this.subscriptionId}async setUserId(e){this.userId=e,localStorage.setItem("rivium_push_user_id",e),await this.registerDevice({userId:e}),this.log(r.INFO,"User ID set:",e)}clearUserId(){this.userId=null,localStorage.removeItem("rivium_push_user_id"),this.log(r.INFO,"User ID cleared")}getUserId(){return this.userId}getInitialMessage(){return this.initialMessage}getBadgeCount(){return this.badgeCount}setBadgeCount(e){this.badgeCount=Math.max(0,e),localStorage.setItem("rivium_push_badge_count",this.badgeCount.toString()),this.updateFaviconBadge(this.badgeCount),"setAppBadge"in navigator&&(this.badgeCount>0?navigator.setAppBadge(this.badgeCount):navigator.clearAppBadge())}clearBadge(){this.setBadgeCount(0)}async refreshMqttToken(){if(!this.deviceId)throw new o(n.NOT_INITIALIZED,"Device not registered");try{const e=await fetch(`${l}/devices/${this.deviceId}/mqtt-token/refresh`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok){const t=await e.json().catch(()=>({}));throw new o(n.SERVER_ERROR,t.message||`HTTP ${e.status}`)}const t=await e.json();t.token&&this.mqttConfig&&(this.mqttConfig.token=t.token,this.log(r.INFO,"MQTT token refreshed successfully"))}catch(e){if(e instanceof o)throw e;throw new o(n.NETWORK_ERROR,e.message)}}getNetworkState(){const e=navigator.connection;return{isAvailable:navigator.onLine,networkType:this.detectNetworkType(),effectiveType:null==e?void 0:e.effectiveType,downlink:null==e?void 0:e.downlink,rtt:null==e?void 0:e.rtt}}getAppState(){return{isVisible:"visible"===document.visibilityState,visibilityState:document.visibilityState}}static isSupported(){return"undefined"!=typeof window&&"Notification"in window&&"serviceWorker"in navigator}static getPermissionStatus(){return"undefined"==typeof Notification?"denied":Notification.permission}onMessage(e){return this.onMessageCallback=e,()=>{this.onMessageCallback=null}}onConnectionState(e){return this.onConnectionStateCallback=e,()=>{this.onConnectionStateCallback=null}}onRegistered(e){return this.onRegisteredCallback=e,()=>{this.onRegisteredCallback=null}}onError(e){return this.onErrorCallback=e,()=>{this.onErrorCallback=null}}onDetailedError(e){return this.onDetailedErrorCallback=e,()=>{this.onDetailedErrorCallback=null}}onNotificationClick(e){return this.onNotificationClickCallback=e,()=>{this.onNotificationClickCallback=null}}onActionClicked(e){return this.onActionClickedCallback=e,()=>{this.onActionClickedCallback=null}}onReconnecting(e){return this.onReconnectingCallback=e,()=>{this.onReconnectingCallback=null}}onNetworkState(e){return this.onNetworkStateCallback=e,()=>{this.onNetworkStateCallback=null}}onAppState(e){return this.onAppStateCallback=e,()=>{this.onAppStateCallback=null}}detectNetworkType(){const e=navigator.connection;if(!e)return c.UNKNOWN;switch(e.type){case"wifi":return c.WIFI;case"cellular":return c.CELLULAR;case"ethernet":return c.ETHERNET;case"none":return c.NONE;default:return c.UNKNOWN}}handleNetworkChange(){const e=this.getNetworkState();this.log(r.DEBUG,"Network state changed:",e),this.onNetworkStateCallback&&this.onNetworkStateCallback(e),this.trackEvent(a.NETWORK_STATE_CHANGED,{isAvailable:e.isAvailable,networkType:e.networkType,effectiveType:e.effectiveType})}handleOnline(){this.log(r.INFO,"Network online"),this.handleNetworkChange(),"disconnected"===this.connectionState&&this.deviceId&&(this.log(r.INFO,"Reconnecting after network restored"),this.connectToGateway())}handleOffline(){this.log(r.INFO,"Network offline"),this.handleNetworkChange()}handleVisibilityChange(){const e=this.getAppState();this.log(r.DEBUG,"App state changed:",e),this.onAppStateCallback&&this.onAppStateCallback(e),this.trackEvent(a.APP_STATE_CHANGED,{isVisible:e.isVisible,visibilityState:e.visibilityState}),e.isVisible&&"disconnected"===this.connectionState&&this.deviceId&&(this.log(r.INFO,"Reconnecting after becoming visible"),this.connectToGateway())}checkInitialMessage(){if("undefined"!=typeof window){const e=new URLSearchParams(window.location.search).get("rivium_push_notification");if(e)try{this.initialMessage=JSON.parse(decodeURIComponent(e)),this.log(r.INFO,"Initial message found:",this.initialMessage)}catch(e){this.log(r.WARNING,"Failed to parse initial message:",e)}const t=sessionStorage.getItem("rivium_push_initial_message");if(t&&!this.initialMessage)try{this.initialMessage=JSON.parse(t),sessionStorage.removeItem("rivium_push_initial_message"),this.log(r.INFO,"Initial message from session:",this.initialMessage)}catch(e){this.log(r.WARNING,"Failed to parse stored message:",e)}}}async registerServiceWorker(){if(!("serviceWorker"in navigator))throw new o(n.SERVICE_NOT_RUNNING,"Service Workers not supported");try{this.serviceWorkerRegistration=await navigator.serviceWorker.register(this.config.serviceWorkerPath,{scope:"/"}),this.log(r.INFO,"Service Worker registered")}catch(e){throw this.log(r.ERROR,"Service Worker registration failed:",e),new o(n.SERVICE_NOT_RUNNING,e.message)}}async requestNotificationPermission(){return"undefined"==typeof Notification?"denied":"granted"===Notification.permission?"granted":await Notification.requestPermission()}async subscribeToPush(e){if(!this.serviceWorkerRegistration)throw new o(n.SERVICE_NOT_RUNNING,"Service Worker not registered");const t=await this.serviceWorkerRegistration.pushManager.getSubscription();if(t)return this.log(r.DEBUG,"Using existing Push subscription"),t;const i=await this.serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:this.urlBase64ToUint8Array(e)});return this.log(r.INFO,"Push subscription created"),i}async registerDevice(e){var t,i,s;try{const a={deviceId:this.deviceId,platform:"web",userId:null==e?void 0:e.userId,appIdentifier:"undefined"!=typeof window?window.location.origin:void 0,metadata:{...null==e?void 0:e.metadata,userAgent:navigator.userAgent,language:navigator.language,url:window.location.origin}};if(this.pushSubscription){const e=this.pushSubscription.toJSON();a.webPushSubscription={endpoint:e.endpoint,keys:{p256dh:(null===(t=e.keys)||void 0===t?void 0:t.p256dh)||"",auth:(null===(i=e.keys)||void 0===i?void 0:i.auth)||""}},this.log(r.DEBUG,"Sending Web Push subscription to server")}const c=await fetch(`${l}/devices/register`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify(a)});if(!c.ok){const e=await c.json().catch(()=>({}));throw new o(n.SERVER_ERROR,e.message||`HTTP ${c.status}`)}const h=await c.json();return h.appId&&(this.appId=h.appId),h.subscriptionId&&(this.subscriptionId=h.subscriptionId,localStorage.setItem("rivium_push_subscription_id",h.subscriptionId),this.log(r.DEBUG,`Stored subscriptionId: ${h.subscriptionId}`)),h.appIdentifier&&(this.appIdentifier=h.appIdentifier),(null===(s=h.mqtt)||void 0===s?void 0:s.token)&&this.mqttConfig&&(this.mqttConfig.token=h.mqtt.token,this.log(r.DEBUG,"Connection token received from registration")),h}catch(e){if(e instanceof o)throw e;throw new o(n.NETWORK_ERROR,e.message)}}connectToGateway(){if(this.pnSocket&&this.pnSocket.close(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),!this.mqttConfig)return this.log(r.WARNING,"Gateway config not available, retrying in 2s..."),void setTimeout(()=>this.connectToGateway(),2e3);if(!this.mqttConfig.token)return this.log(r.ERROR,"Connection token not available. Device must be registered first."),void this.emitError(n.AUTHENTICATION_FAILED,"Connection token not available");this.setConnectionState("connecting");const s=this.mqttConfig.wsHost||this.mqttConfig.host,o=this.mqttConfig.wsPort,c="undefined"!=typeof window&&"https:"===window.location.protocol||443===o;this.log(r.DEBUG,`Connecting to gateway (secure: ${c})`);const l=`rivium_push_${this.appId}_${this.deviceId}`,h=(new e).gateway(s).port(o).clientId(l).auth(t.token(this.mqttConfig.token)).secure(c).freshStart(!1).autoReconnect(!1).connectionTimeout(10).build();this.pnSocket=new i(h);const d={onStateChanged:e=>{this.log(r.DEBUG,`Connection state changed: ${e}`)},onConnected:()=>{this.log(r.INFO,"Connected to gateway"),this.setConnectionState("connected"),this.reconnectAttempts=0,this.trackEvent(a.CONNECTED);const e=this.config.apiKey.substring(0,16),t=this.appIdentifier||("undefined"!=typeof window?window.location.origin:"_default");if(this.subscriptionId){const t=`rivium_push/${e}/sub/${this.subscriptionId}`;this.pnSocket.stream(t,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.DEBUG,`Streaming from subscription channel ${t}`)}const i=`rivium_push/${e}/broadcast`;this.pnSocket.stream(i,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.DEBUG,"Streaming from broadcast channel");const n=`rivium_push/${e}/${this.deviceId}/${t}`;this.pnSocket.stream(n,e=>{this.handlePNMessage(e)},this.config.mqttQos),this.log(r.DEBUG,"Streaming from (deprecated) device channel"),this.subscribedTopics.forEach(t=>{const i=`rivium_push/${e}/topic/${t}`;this.pnSocket.stream(i,e=>{this.handlePNMessage(e)},this.config.mqttQos)})},onDisconnected:e=>{this.log(r.INFO,"Disconnected from gateway",e||""),this.setConnectionState("disconnected"),this.trackEvent(a.DISCONNECTED),this.scheduleReconnect()},onReconnecting:(e,t)=>{this.log(r.INFO,`Reconnecting attempt ${e} in ${t}ms`)}};this.pnSocket.addConnectionListener(d),this.pnSocket.addErrorListener(e=>{this.log(r.ERROR,"Gateway error:",e.message),this.setConnectionState("error");let t=n.CONNECTION_FAILED;const i=e.message.toLowerCase();if(i.includes("timeout"))t=n.CONNECTION_TIMEOUT;else if(i.includes("refused")||i.includes("not authorized")){if(t=n.CONNECTION_REFUSED,i.includes("not authorized"))return this.log(r.INFO,"Token may be expired, attempting refresh..."),void this.refreshMqttToken().then(()=>{this.log(r.INFO,"Token refreshed, reconnecting..."),this.connectToGateway()}).catch(e=>{this.log(r.ERROR,"Token refresh failed:",e),this.emitError(n.AUTHENTICATION_FAILED,"Token expired and refresh failed")})}else i.includes("auth")||i.includes("credential")?t=n.AUTHENTICATION_FAILED:(i.includes("ssl")||i.includes("tls"))&&(t=n.SSL_ERROR);this.emitError(t,e.message)}),this.log(r.DEBUG,"Opening connection to gateway..."),this.pnSocket.open()}handlePNMessage(e){try{const t=e.payloadAsJson();this.handleMqttMessage(e.channel,t)}catch(e){this.log(r.ERROR,"Message parse error:",e),this.emitError(n.MESSAGE_PARSE_ERROR,e.message)}}disconnectFromGateway(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pnSocket&&(this.pnSocket.close(),this.pnSocket=null),this.setConnectionState("disconnected")}scheduleReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return this.log(r.WARNING,"Max reconnect attempts reached"),void this.emitError(n.CONNECTION_FAILED,"Max reconnect attempts reached");if(!navigator.onLine)return void this.log(r.DEBUG,"Offline, skipping reconnect");const e=Math.min(1e3*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.log(r.INFO,`Reconnecting in ${e}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);const t={retryAttempt:this.reconnectAttempts,nextRetryMs:e,maxRetryAttempts:this.maxReconnectAttempts};this.onReconnectingCallback&&this.onReconnectingCallback(t),this.trackEvent(a.RETRY_STARTED,{retryAttempt:this.reconnectAttempts,nextRetryMs:e}),this.reconnectTimer=setTimeout(()=>{this.connectToGateway()},e)}handleMqttMessage(e,t){var i;const n=this.normalizeMessage(t);this.log(r.DEBUG,"Message received:",n.title),this.trackEvent(a.MESSAGE_RECEIVED,{messageId:n.messageId,title:n.title,silent:n.silent,hasImage:!!n.imageUrl,hasActions:!!(null===(i=n.actions)||void 0===i?void 0:i.length)}),this.handleBadge(n),n.silent||"visible"===document.visibilityState||(this.showRichNotification(n),this.trackEvent(a.MESSAGE_DISPLAYED,{messageId:n.messageId,title:n.title})),this.onMessageCallback&&this.onMessageCallback(n)}normalizeMessage(e){const t=this.getLocalizedContent(e,"title"),i=this.getLocalizedContent(e,"body");return{title:t||e.title||"",body:i||e.body||"",data:e.data,silent:e.silent,imageUrl:e.imageUrl||e.image,iconUrl:e.iconUrl||e.icon,actions:e.actions,deepLink:e.deepLink,badge:e.badge,badgeAction:e.badgeAction,sound:e.sound,threadId:e.threadId,collapseKey:e.collapseKey,category:e.category,priority:e.priority,ttl:e.ttl,localizations:e.localizations,timezone:e.timezone,messageId:e.messageId,campaignId:e.campaignId,icon:e.iconUrl||e.icon,image:e.imageUrl||e.image,tag:e.tag||e.collapseKey||e.threadId}}getLocalizedContent(e,t){if(!e.localizations||!Array.isArray(e.localizations))return null;const i=navigator.language.split("-")[0].toLowerCase(),n=e.localizations.find(e=>e.locale.toLowerCase().startsWith(i));return n?n[t]:null}handleBadge(e){if(void 0===e.badge&&!e.badgeAction)return;const t=e.badgeAction||"set";let i=e.badge||0;switch(t){case"set":i=e.badge||0;break;case"increment":i=this.badgeCount+(e.badge||1);break;case"decrement":i=Math.max(0,this.badgeCount-(e.badge||1));break;case"clear":i=0}this.setBadgeCount(i)}handleServiceWorkerMessage(e){var t;const i=e.data;if("push-message"===i.type){this.log(r.DEBUG,"Push message received from SW:",null===(t=i.message)||void 0===t?void 0:t.title);const e=this.normalizeMessage(i.message);this.trackEvent(a.MESSAGE_RECEIVED,{messageId:e.messageId,title:e.title,source:"web-push"}),this.handleBadge(e),this.onMessageCallback&&this.onMessageCallback(e)}else if("notification-click"===i.type){this.log(r.INFO,"Notification clicked from SW");const e=this.normalizeMessage(i.message||{});this.trackEvent(a.NOTIFICATION_CLICKED,{messageId:e.messageId,title:e.title,action:i.action}),this.onNotificationClickCallback&&this.onNotificationClickCallback(e,i.action)}else if("action-clicked"===i.type){this.log(r.INFO,"Action clicked from SW:",i.actionId);const e=this.normalizeMessage(i.message||{});this.trackEvent(a.ACTION_CLICKED,{actionId:i.actionId,messageId:e.messageId,title:e.title}),this.onActionClickedCallback&&this.onActionClickedCallback(i.actionId,e)}else"initial-message"===i.type?(this.log(r.INFO,"Initial message received from SW"),this.initialMessage=this.normalizeMessage(i.message||{}),this.onNotificationClickCallback&&this.initialMessage&&this.onNotificationClickCallback(this.initialMessage,void 0)):"navigate"===i.type&&(this.log(r.INFO,"Navigating to:",i.url),i.url&&"undefined"!=typeof window&&(window.location.href=i.url))}showRichNotification(e){if("granted"!==Notification.permission)return;const t={body:e.body,icon:e.iconUrl||e.icon,badge:e.iconUrl||e.icon,image:e.imageUrl||e.image,tag:e.tag||e.collapseKey||e.threadId,data:{...e.data,deepLink:e.deepLink,messageId:e.messageId,campaignId:e.campaignId,riviumPushMessage:e},requireInteraction:"high"===e.priority,silent:"none"===e.sound};if(e.actions&&e.actions.length>0){const i=e.actions.slice(0,2).map(e=>({action:e.id,title:e.title,icon:e.icon}));this.serviceWorkerRegistration&&(t.actions=i)}if(this.serviceWorkerRegistration)this.serviceWorkerRegistration.showNotification(e.title,t);else{const i=new Notification(e.title,t);i.onclick=()=>{window.focus(),e.deepLink&&(window.location.href=e.deepLink),this.onNotificationClickCallback&&this.onNotificationClickCallback(e),this.trackEvent(a.NOTIFICATION_CLICKED,{messageId:e.messageId,title:e.title}),i.close()}}}updateFaviconBadge(e){try{const t=document.createElement("canvas"),i=t.getContext("2d");if(!i)return;const n=32;t.width=n,t.height=n;const s=document.querySelector('link[rel="icon"]'),o=(null==s?void 0:s.href)||"/favicon.ico",a=new Image;a.crossOrigin="anonymous",a.onload=()=>{if(i.drawImage(a,0,0,n,n),e>0){const t=14,s=n-t/2,o=t/2;i.beginPath(),i.arc(s,o,t/2+1,0,2*Math.PI),i.fillStyle="#ef4444",i.fill(),i.fillStyle="#ffffff",i.font="bold 10px sans-serif",i.textAlign="center",i.textBaseline="middle",i.fillText(e>99?"99+":e.toString(),s,o)}const o=document.createElement("link");o.rel="icon",o.href=t.toDataURL("image/png"),s&&s.remove(),document.head.appendChild(o)},a.src=o}catch(e){this.log(r.WARNING,"Could not update favicon badge:",e)}}setConnectionState(e){this.connectionState=e,this.onConnectionStateCallback&&this.onConnectionStateCallback(e)}getOrCreateDeviceId(){const e="rivium_push_device_id";let t=localStorage.getItem(e);return t||(t="web_"+this.generateUUID(),localStorage.setItem(e,t)),t}generateUUID(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}urlBase64ToUint8Array(e){const t=(e+"=".repeat((4-e.length%4)%4)).replace(/-/g,"+").replace(/_/g,"/"),i=window.atob(t),n=new Uint8Array(i.length);for(let e=0;e<i.length;++e)n[e]=i.charCodeAt(e);return n}}export{c as NetworkType,h as RiviumPush,a as RiviumPushAnalyticsEvent,o as RiviumPushError,n as RiviumPushErrorCode,r as RiviumPushLogLevel,h as default};
|
|
2
2
|
//# sourceMappingURL=index.esm.js.map
|