@notifyon/web
Advanced tools
+1
-1
@@ -17,3 +17,3 @@ interface NotifyOnWebConfig { | ||
| readonly config: NotifyOnWebConfig; | ||
| private ably; | ||
| private pusher; | ||
| private channel; | ||
@@ -20,0 +20,0 @@ private sound; |
+1
-1
@@ -17,3 +17,3 @@ interface NotifyOnWebConfig { | ||
| readonly config: NotifyOnWebConfig; | ||
| private ably; | ||
| private pusher; | ||
| private channel; | ||
@@ -20,0 +20,0 @@ private sound; |
+3
-3
@@ -1,6 +0,6 @@ | ||
| 'use strict';var h=(s,e,n)=>new Promise((o,r)=>{var p=i=>{try{t(n.next(i));}catch(l){r(l);}},a=i=>{try{t(n.throw(i));}catch(l){r(l);}},t=i=>i.done?o(i.value):Promise.resolve(i.value).then(p,a);t((n=n.apply(s,e)).next());});var u=class extends Error{constructor(e){super(e),this.name="NotifyOnWebError";}};var y=class{constructor(){this.audioContext=null;}play(){return h(this,null,function*(){try{this.audioContext||(this.audioContext=new(window.AudioContext||window.webkitAudioContext));let e=this.audioContext.currentTime;this.playTone(349,e,.1,.18,!0),this.playTone(440,e+.08,.08,.22,!0),this.playTone(523,e+.15,.04,.15,!1);}catch(e){}})}playTone(e,n,o,r,p=false){if(!this.audioContext)return;let a=this.audioContext.createOscillator(),t=this.audioContext.createGain(),i=this.audioContext.createDelay(.5);i.delayTime.value=.02;let l=this.audioContext.createGain();l.gain.value=.15;let d=this.audioContext.createBiquadFilter();if(d.type="lowpass",d.frequency.value=1800,d.Q.value=1.2,a.connect(d),d.connect(t),t.connect(this.audioContext.destination),t.connect(i),i.connect(l),l.connect(this.audioContext.destination),a.type="sine",a.frequency.value=e,p){let f=this.audioContext.createOscillator(),b=this.audioContext.createGain();f.frequency.value=5,b.gain.value=2,f.connect(b),b.connect(a.frequency),f.start(n),f.stop(n+r);}t.gain.setValueAtTime(1e-5,n),t.gain.exponentialRampToValueAtTime(o,n+.01),t.gain.setValueAtTime(o*.9,n+r-.06),t.gain.exponentialRampToValueAtTime(1e-5,n+r),a.start(n),a.stop(n+r);}destroy(){this.audioContext&&(this.audioContext.close(),this.audioContext=null);}};var m="CF7JaA.MUFxFA:-9iIuCIf6yhXp5SV31j4Pvy8SreELcO_04ifYgleK5E",c=null,g=class{constructor(e){this.ably=null;this.channel=null;this.connected=false;if(!e.publicKey)throw new u("Public key is required");if(!e.userId)throw new u("User ID is required");this.config=e,this.sound=new y,this.connect();}connect(){return h(this,null,function*(){if(!this.connected)try{let{default:e}=yield import('ably');this.ably=new e.Realtime({key:m,logLevel:0});let n=`project:${this.config.publicKey}:user:${this.config.userId}`;this.channel=this.ably.channels.get(n),this.channel.subscribe("notification",o=>{this.handleNotification(o.data);}),this.ably.connection.on("connected",()=>{this.connected=!0;}),this.ably.connection.on("disconnected",()=>{this.connected=!1;}),this.ably.connection.on("failed",()=>{this.connected=!1;});}catch(e){throw new u(`Failed to connect: ${e}`)}})}disconnect(){this.channel&&(this.channel.unsubscribe(),this.channel=null),this.ably&&(this.ably.close(),this.ably=null,this.connected=false);}destroy(){this.disconnect(),this.sound.destroy(),c===this&&(c=null);}handleNotification(e){try{let n=e.channels||{sound:!0,push:!1};n.sound&&this.sound.play(),n.push&&(document.hidden||!document.hasFocus())&&this.handlePushNotification(e);}catch(n){}}handlePushNotification(e){if("Notification"in window)if(Notification.permission==="granted")try{let n=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| 'use strict';var x=require('pusher-js');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var x__default=/*#__PURE__*/_interopDefault(x);var v=(o,e,n)=>new Promise((s,r)=>{var p=i=>{try{t(n.next(i));}catch(u){r(u);}},c=i=>{try{t(n.throw(i));}catch(u){r(u);}},t=i=>i.done?s(i.value):Promise.resolve(i.value).then(p,c);t((n=n.apply(o,e)).next());});var l=class extends Error{constructor(e){super(e),this.name="NotifyOnWebError";}};var f=class{constructor(){this.audioContext=null;}play(){return v(this,null,function*(){try{this.audioContext||(this.audioContext=new(window.AudioContext||window.webkitAudioContext));let e=this.audioContext.currentTime;this.playTone(349,e,.1,.18,!0),this.playTone(440,e+.08,.08,.22,!0),this.playTone(523,e+.15,.04,.15,!1);}catch(e){}})}playTone(e,n,s,r,p=false){if(!this.audioContext)return;let c=this.audioContext.createOscillator(),t=this.audioContext.createGain(),i=this.audioContext.createDelay(.5);i.delayTime.value=.02;let u=this.audioContext.createGain();u.gain.value=.15;let d=this.audioContext.createBiquadFilter();if(d.type="lowpass",d.frequency.value=1800,d.Q.value=1.2,c.connect(d),d.connect(t),t.connect(this.audioContext.destination),t.connect(i),i.connect(u),u.connect(this.audioContext.destination),c.type="sine",c.frequency.value=e,p){let h=this.audioContext.createOscillator(),y=this.audioContext.createGain();h.frequency.value=5,y.gain.value=2,h.connect(y),y.connect(c.frequency),h.start(n),h.stop(n+r);}t.gain.setValueAtTime(1e-5,n),t.gain.exponentialRampToValueAtTime(s,n+.01),t.gain.setValueAtTime(s*.9,n+r-.06),t.gain.exponentialRampToValueAtTime(1e-5,n+r),c.start(n),c.stop(n+r);}destroy(){this.audioContext&&(this.audioContext.close(),this.audioContext=null);}};var g="70225167dde6bac400ea",m="mt1",a=null,b=class{constructor(e){this.pusher=null;this.channel=null;this.connected=false;if(!e.publicKey)throw new l("Public key is required");if(!e.userId)throw new l("User ID is required");this.config=e,this.sound=new f,this.connect();}connect(){if(!this.connected)try{this.pusher=new x__default.default(g,{cluster:m});let e=`project-${this.config.publicKey}-user-${this.config.userId}`;this.channel=this.pusher.subscribe(e),this.channel.bind("notification",n=>{this.handleNotification(n);}),this.pusher.connection.bind("connected",()=>{this.connected=!0;}),this.pusher.connection.bind("disconnected",()=>{this.connected=!1;}),this.pusher.connection.bind("failed",()=>{this.connected=!1;});}catch(e){throw new l(`Failed to connect: ${e}`)}}disconnect(){this.channel&&this.pusher&&(this.pusher.unsubscribe(this.channel.name),this.channel=null),this.pusher&&(this.pusher.disconnect(),this.pusher=null,this.connected=false);}destroy(){this.disconnect(),this.sound.destroy(),a===this&&(a=null);}handleNotification(e){try{let n=e.channels||{sound:!0,push:!1};n.sound&&this.sound.play(),n.push&&(document.hidden||!document.hasFocus())&&this.handlePushNotification(e);}catch(n){}}handlePushNotification(e){if("Notification"in window)if(Notification.permission==="granted")try{let n=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:!1,silent:!1});n.onclick=()=>{window.focus(),n.close();};}catch(n){}else Notification.permission!=="denied"&&Notification.requestPermission().then(n=>{if(n==="granted"){let o=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:!1,silent:!1});n.onclick=()=>{window.focus(),n.close();};}catch(n){}else Notification.permission!=="denied"&&Notification.requestPermission().then(n=>{if(n==="granted"){let s=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:false,silent:false});o.onclick=()=>{window.focus(),o.close();};}});}};function v(s){if(c){let e=c.config;if(e.publicKey===s.publicKey&&e.userId===s.userId)return c;c.destroy();}return c=new g(s),c}exports.connect=v;//# sourceMappingURL=index.js.map | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:false,silent:false});s.onclick=()=>{window.focus(),s.close();};}});}};function C(o){if(a){let e=a.config;if(e.publicKey===o.publicKey&&e.userId===o.userId)return a;a.destroy();}return a=new b(o),a}exports.connect=C;//# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/types.ts","../src/sound.ts","../src/client.ts"],"names":["NotifyOnWebError","message","NotificationSound","__async","now","error","frequency","startTime","volume","duration","addVibrato","oscillator","gainNode","delay","delayGain","filter","vibrato","vibratoGain","ABLY_SUBSCRIBE_KEY","globalInstance","NotifyOnWeb","config","AblyLib","channelName","data","channels","notification","permission","connect","existingConfig"],"mappings":"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","file":"index.js","sourcesContent":["export interface NotifyOnWebConfig {\n publicKey: string; // NotifyOn project public key\n userId: string;\n}\n\nexport interface NotificationEvent {\n id: string;\n message: string;\n timestamp: string;\n channels?: {\n sound?: boolean;\n push?: boolean;\n };\n}\n\nexport class NotifyOnWebError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NotifyOnWebError';\n }\n}","export class NotificationSound {\n private audioContext: AudioContext | null = null;\n\n /**\n * Play a notification sound using Web Audio API\n */\n async play(): Promise<void> {\n try {\n // Create audio context if not exists\n if (!this.audioContext) {\n this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n\n const now = this.audioContext.currentTime;\n \n // Create a natural, pleasant chime with mid-range tones\n // First tone - F4 (349Hz) with slight vibrato for naturalness\n this.playTone(349, now, 0.1, 0.18, true);\n \n // Second tone - A4 (440Hz) - major third up, very harmonious\n this.playTone(440, now + 0.08, 0.08, 0.22, true);\n \n // Third soft tone - C5 (523Hz) - adds sparkle\n this.playTone(523, now + 0.15, 0.04, 0.15, false);\n \n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private playTone(frequency: number, startTime: number, volume: number, duration: number, addVibrato: boolean = false): void {\n if (!this.audioContext) return;\n \n // Create oscillator and nodes for this tone\n const oscillator = this.audioContext.createOscillator();\n const gainNode = this.audioContext.createGain();\n \n // Add reverb-like effect with delay for warmth\n const delay = this.audioContext.createDelay(0.5);\n delay.delayTime.value = 0.02;\n \n const delayGain = this.audioContext.createGain();\n delayGain.gain.value = 0.15; // Very subtle reverb\n \n // Add a low-pass filter with some resonance for character\n const filter = this.audioContext.createBiquadFilter();\n filter.type = 'lowpass';\n filter.frequency.value = 1800; // Balanced cutoff\n filter.Q.value = 1.2; // Slight resonance for character\n \n // Connect main signal path: oscillator -> filter -> gain -> destination\n oscillator.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(this.audioContext.destination);\n \n // Connect delay path for subtle reverb: gain -> delay -> delayGain -> destination\n gainNode.connect(delay);\n delay.connect(delayGain);\n delayGain.connect(this.audioContext.destination);\n \n // Use sine wave for the most gentle sound\n oscillator.type = 'sine';\n oscillator.frequency.value = frequency;\n \n // Add subtle vibrato for more natural, less mechanical sound\n if (addVibrato) {\n const vibrato = this.audioContext.createOscillator();\n const vibratoGain = this.audioContext.createGain();\n vibrato.frequency.value = 5; // 5Hz vibrato rate\n vibratoGain.gain.value = 2; // Very subtle frequency modulation\n vibrato.connect(vibratoGain);\n vibratoGain.connect(oscillator.frequency);\n vibrato.start(startTime);\n vibrato.stop(startTime + duration);\n }\n \n // Natural envelope with varied attack for organic feel\n gainNode.gain.setValueAtTime(0.00001, startTime);\n gainNode.gain.exponentialRampToValueAtTime(volume, startTime + 0.01); // Quick but soft attack\n gainNode.gain.setValueAtTime(volume * 0.9, startTime + duration - 0.06); // Slight decay\n gainNode.gain.exponentialRampToValueAtTime(0.00001, startTime + duration); // Natural release\n \n // Play the tone\n oscillator.start(startTime);\n oscillator.stop(startTime + duration);\n }\n\n /**\n * Clean up audio context\n */\n destroy(): void {\n if (this.audioContext) {\n this.audioContext.close();\n this.audioContext = null;\n }\n }\n}","import type { NotifyOnWebConfig, NotificationEvent } from \"./types\";\nimport { NotifyOnWebError } from \"./types\";\nimport { NotificationSound } from \"./sound\";\n\n// Type-only import for TypeScript\nimport type Ably from \"ably\";\n\nconst ABLY_SUBSCRIBE_KEY =\n \"CF7JaA.MUFxFA:-9iIuCIf6yhXp5SV31j4Pvy8SreELcO_04ifYgleK5E\";\n\n// Singleton instance storage\nlet globalInstance: NotifyOnWeb | null = null;\n\nexport class NotifyOnWeb {\n public readonly config: NotifyOnWebConfig;\n private ably: Ably.Realtime | null = null;\n private channel: Ably.RealtimeChannel | null = null;\n private sound: NotificationSound;\n private connected = false;\n\n constructor(config: NotifyOnWebConfig) {\n if (!config.publicKey) {\n throw new NotifyOnWebError(\"Public key is required\");\n }\n if (!config.userId) {\n throw new NotifyOnWebError(\"User ID is required\");\n }\n\n this.config = config;\n\n this.sound = new NotificationSound();\n\n // Auto-connect on instantiation\n this.connect();\n }\n\n /**\n * Start listening for notifications (called automatically)\n */\n private async connect(): Promise<void> {\n if (this.connected) {\n return;\n }\n\n try {\n // Dynamically import Ably only on client side\n const { default: AblyLib } = await import(\"ably\");\n \n // Initialize Ably with our internal subscribe key\n this.ably = new AblyLib.Realtime({\n key: ABLY_SUBSCRIBE_KEY,\n // Optional: log level for debugging\n logLevel: 0, // 0 = verbose, 4 = none\n });\n\n // Use last 8 characters of public key for channel naming\n // This provides enough uniqueness while keeping channel names manageable\n const channelName = `project:${this.config.publicKey}:user:${this.config.userId}`;\n this.channel = this.ably.channels.get(channelName);\n\n // Subscribe to notification messages\n this.channel.subscribe(\"notification\", (message) => {\n this.handleNotification(message.data);\n });\n\n // Monitor connection state\n this.ably.connection.on(\"connected\", () => {\n this.connected = true;\n });\n\n this.ably.connection.on(\"disconnected\", () => {\n this.connected = false;\n });\n\n this.ably.connection.on(\"failed\", () => {\n this.connected = false;\n });\n } catch (error) {\n throw new NotifyOnWebError(`Failed to connect: ${error}`);\n }\n }\n\n /**\n * Stop listening for notifications (called automatically on destroy)\n */\n private disconnect(): void {\n if (this.channel) {\n this.channel.unsubscribe();\n this.channel = null;\n }\n\n if (this.ably) {\n this.ably.close();\n this.ably = null;\n this.connected = false;\n }\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this.disconnect();\n this.sound.destroy();\n // Clear global instance if this is the one being destroyed\n if (globalInstance === this) {\n globalInstance = null;\n }\n }\n\n private handleNotification(data: NotificationEvent): void {\n try {\n // Check which channels are enabled for this notification\n const channels = data.channels || { sound: true, push: false }; // Default to sound enabled\n\n // Progressive notification strategy:\n // 1. Always play sound if enabled\n // 2. Show push only if enabled AND tab is not visible\n\n // Handle sound notification (always if enabled)\n if (channels.sound) {\n this.sound.play();\n }\n\n // Handle push notification (only if tab is not visible/focused)\n if (channels.push) {\n // Check if document is hidden or not focused\n const isTabHidden = document.hidden || !document.hasFocus();\n\n if (isTabHidden) {\n this.handlePushNotification(data);\n }\n }\n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private handlePushNotification(data: NotificationEvent): void {\n // Check if browser supports notifications\n if (!(\"Notification\" in window)) {\n return;\n }\n\n // Check notification permission\n if (Notification.permission === \"granted\") {\n // Create push notification\n try {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n } catch (error) {\n // Silently handle notification creation errors\n }\n } else if (Notification.permission !== \"denied\") {\n // Request permission if not yet decided\n Notification.requestPermission().then((permission) => {\n if (permission === \"granted\") {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n }\n });\n }\n }\n}\n\n// Simple function API with singleton pattern\nexport function connect(config: NotifyOnWebConfig): NotifyOnWeb {\n // Check if we already have a connection with the same config\n if (globalInstance) {\n const existingConfig = globalInstance.config;\n // If connecting with same publicKey and userId, return existing instance\n if (\n existingConfig.publicKey === config.publicKey &&\n existingConfig.userId === config.userId\n ) {\n return globalInstance;\n }\n // If different config, destroy old connection and create new one\n globalInstance.destroy();\n }\n \n // Create and store new instance\n globalInstance = new NotifyOnWeb(config);\n return globalInstance;\n}\n"]} | ||
| {"version":3,"sources":["../src/types.ts","../src/sound.ts","../src/client.ts"],"names":["NotifyOnWebError","message","NotificationSound","__async","now","error","frequency","startTime","volume","duration","addVibrato","oscillator","gainNode","delay","delayGain","filter","vibrato","vibratoGain","PUSHER_APP_KEY","PUSHER_APP_CLUSTER","globalInstance","NotifyOnWeb","config","Pusher","channelName","data","channels","notification","permission","connect","existingConfig"],"mappings":"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","file":"index.js","sourcesContent":["export interface NotifyOnWebConfig {\n publicKey: string; // NotifyOn project public key\n userId: string;\n}\n\nexport interface NotificationEvent {\n id: string;\n message: string;\n timestamp: string;\n channels?: {\n sound?: boolean;\n push?: boolean;\n };\n}\n\nexport class NotifyOnWebError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NotifyOnWebError\";\n }\n}\n","export class NotificationSound {\n private audioContext: AudioContext | null = null;\n\n /**\n * Play a notification sound using Web Audio API\n */\n async play(): Promise<void> {\n try {\n // Create audio context if not exists\n if (!this.audioContext) {\n this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n\n const now = this.audioContext.currentTime;\n \n // Create a natural, pleasant chime with mid-range tones\n // First tone - F4 (349Hz) with slight vibrato for naturalness\n this.playTone(349, now, 0.1, 0.18, true);\n \n // Second tone - A4 (440Hz) - major third up, very harmonious\n this.playTone(440, now + 0.08, 0.08, 0.22, true);\n \n // Third soft tone - C5 (523Hz) - adds sparkle\n this.playTone(523, now + 0.15, 0.04, 0.15, false);\n \n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private playTone(frequency: number, startTime: number, volume: number, duration: number, addVibrato: boolean = false): void {\n if (!this.audioContext) return;\n \n // Create oscillator and nodes for this tone\n const oscillator = this.audioContext.createOscillator();\n const gainNode = this.audioContext.createGain();\n \n // Add reverb-like effect with delay for warmth\n const delay = this.audioContext.createDelay(0.5);\n delay.delayTime.value = 0.02;\n \n const delayGain = this.audioContext.createGain();\n delayGain.gain.value = 0.15; // Very subtle reverb\n \n // Add a low-pass filter with some resonance for character\n const filter = this.audioContext.createBiquadFilter();\n filter.type = 'lowpass';\n filter.frequency.value = 1800; // Balanced cutoff\n filter.Q.value = 1.2; // Slight resonance for character\n \n // Connect main signal path: oscillator -> filter -> gain -> destination\n oscillator.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(this.audioContext.destination);\n \n // Connect delay path for subtle reverb: gain -> delay -> delayGain -> destination\n gainNode.connect(delay);\n delay.connect(delayGain);\n delayGain.connect(this.audioContext.destination);\n \n // Use sine wave for the most gentle sound\n oscillator.type = 'sine';\n oscillator.frequency.value = frequency;\n \n // Add subtle vibrato for more natural, less mechanical sound\n if (addVibrato) {\n const vibrato = this.audioContext.createOscillator();\n const vibratoGain = this.audioContext.createGain();\n vibrato.frequency.value = 5; // 5Hz vibrato rate\n vibratoGain.gain.value = 2; // Very subtle frequency modulation\n vibrato.connect(vibratoGain);\n vibratoGain.connect(oscillator.frequency);\n vibrato.start(startTime);\n vibrato.stop(startTime + duration);\n }\n \n // Natural envelope with varied attack for organic feel\n gainNode.gain.setValueAtTime(0.00001, startTime);\n gainNode.gain.exponentialRampToValueAtTime(volume, startTime + 0.01); // Quick but soft attack\n gainNode.gain.setValueAtTime(volume * 0.9, startTime + duration - 0.06); // Slight decay\n gainNode.gain.exponentialRampToValueAtTime(0.00001, startTime + duration); // Natural release\n \n // Play the tone\n oscillator.start(startTime);\n oscillator.stop(startTime + duration);\n }\n\n /**\n * Clean up audio context\n */\n destroy(): void {\n if (this.audioContext) {\n this.audioContext.close();\n this.audioContext = null;\n }\n }\n}","import type { NotifyOnWebConfig, NotificationEvent } from \"./types\";\nimport { NotifyOnWebError } from \"./types\";\nimport { NotificationSound } from \"./sound\";\nimport Pusher from \"pusher-js\";\n\nconst PUSHER_APP_KEY = \"70225167dde6bac400ea\";\nconst PUSHER_APP_CLUSTER = \"mt1\";\n\n// Singleton instance storage\nlet globalInstance: NotifyOnWeb | null = null;\n\nexport class NotifyOnWeb {\n public readonly config: NotifyOnWebConfig;\n private pusher: Pusher | null = null;\n private channel: any | null = null;\n private sound: NotificationSound;\n private connected = false;\n\n constructor(config: NotifyOnWebConfig) {\n if (!config.publicKey) {\n throw new NotifyOnWebError(\"Public key is required\");\n }\n if (!config.userId) {\n throw new NotifyOnWebError(\"User ID is required\");\n }\n\n this.config = config;\n\n this.sound = new NotificationSound();\n\n // Auto-connect on instantiation\n this.connect();\n }\n\n /**\n * Start listening for notifications (called automatically)\n */\n private connect(): void {\n if (this.connected) {\n return;\n }\n\n try {\n this.pusher = new Pusher(PUSHER_APP_KEY, {\n cluster: PUSHER_APP_CLUSTER,\n });\n\n // Channel naming convention matches server\n const channelName = `project-${this.config.publicKey}-user-${this.config.userId}`;\n this.channel = this.pusher.subscribe(channelName);\n\n // Subscribe to notification events\n this.channel.bind(\"notification\", (data: any) => {\n this.handleNotification(data);\n });\n\n // Monitor connection state\n this.pusher.connection.bind(\"connected\", () => {\n this.connected = true;\n });\n\n this.pusher.connection.bind(\"disconnected\", () => {\n this.connected = false;\n });\n\n this.pusher.connection.bind(\"failed\", () => {\n this.connected = false;\n });\n } catch (error) {\n throw new NotifyOnWebError(`Failed to connect: ${error}`);\n }\n }\n\n /**\n * Stop listening for notifications (called automatically on destroy)\n */\n private disconnect(): void {\n if (this.channel && this.pusher) {\n this.pusher.unsubscribe(this.channel.name);\n this.channel = null;\n }\n\n if (this.pusher) {\n this.pusher.disconnect();\n this.pusher = null;\n this.connected = false;\n }\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this.disconnect();\n this.sound.destroy();\n // Clear global instance if this is the one being destroyed\n if (globalInstance === this) {\n globalInstance = null;\n }\n }\n\n private handleNotification(data: NotificationEvent): void {\n try {\n // Check which channels are enabled for this notification\n const channels = data.channels || { sound: true, push: false }; // Default to sound enabled\n\n // Progressive notification strategy:\n // 1. Always play sound if enabled\n // 2. Show push only if enabled AND tab is not visible\n\n // Handle sound notification (always if enabled)\n if (channels.sound) {\n this.sound.play();\n }\n\n // Handle push notification (only if tab is not visible/focused)\n if (channels.push) {\n // Check if document is hidden or not focused\n const isTabHidden = document.hidden || !document.hasFocus();\n\n if (isTabHidden) {\n this.handlePushNotification(data);\n }\n }\n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private handlePushNotification(data: NotificationEvent): void {\n // Check if browser supports notifications\n if (!(\"Notification\" in window)) {\n return;\n }\n\n // Check notification permission\n if (Notification.permission === \"granted\") {\n // Create push notification\n try {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n } catch (error) {\n // Silently handle notification creation errors\n }\n } else if (Notification.permission !== \"denied\") {\n // Request permission if not yet decided\n Notification.requestPermission().then((permission) => {\n if (permission === \"granted\") {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n }\n });\n }\n }\n}\n\n// Simple function API with singleton pattern\nexport function connect(config: NotifyOnWebConfig): NotifyOnWeb {\n // Check if we already have a connection with the same config\n if (globalInstance) {\n const existingConfig = globalInstance.config;\n // If connecting with same publicKey and userId, return existing instance\n if (\n existingConfig.publicKey === config.publicKey &&\n existingConfig.userId === config.userId\n ) {\n return globalInstance;\n }\n // If different config, destroy old connection and create new one\n globalInstance.destroy();\n }\n\n // Create and store new instance\n globalInstance = new NotifyOnWeb(config);\n return globalInstance;\n}\n"]} |
+3
-3
@@ -1,6 +0,6 @@ | ||
| var h=(s,e,n)=>new Promise((o,r)=>{var p=i=>{try{t(n.next(i));}catch(l){r(l);}},a=i=>{try{t(n.throw(i));}catch(l){r(l);}},t=i=>i.done?o(i.value):Promise.resolve(i.value).then(p,a);t((n=n.apply(s,e)).next());});var u=class extends Error{constructor(e){super(e),this.name="NotifyOnWebError";}};var y=class{constructor(){this.audioContext=null;}play(){return h(this,null,function*(){try{this.audioContext||(this.audioContext=new(window.AudioContext||window.webkitAudioContext));let e=this.audioContext.currentTime;this.playTone(349,e,.1,.18,!0),this.playTone(440,e+.08,.08,.22,!0),this.playTone(523,e+.15,.04,.15,!1);}catch(e){}})}playTone(e,n,o,r,p=false){if(!this.audioContext)return;let a=this.audioContext.createOscillator(),t=this.audioContext.createGain(),i=this.audioContext.createDelay(.5);i.delayTime.value=.02;let l=this.audioContext.createGain();l.gain.value=.15;let d=this.audioContext.createBiquadFilter();if(d.type="lowpass",d.frequency.value=1800,d.Q.value=1.2,a.connect(d),d.connect(t),t.connect(this.audioContext.destination),t.connect(i),i.connect(l),l.connect(this.audioContext.destination),a.type="sine",a.frequency.value=e,p){let f=this.audioContext.createOscillator(),b=this.audioContext.createGain();f.frequency.value=5,b.gain.value=2,f.connect(b),b.connect(a.frequency),f.start(n),f.stop(n+r);}t.gain.setValueAtTime(1e-5,n),t.gain.exponentialRampToValueAtTime(o,n+.01),t.gain.setValueAtTime(o*.9,n+r-.06),t.gain.exponentialRampToValueAtTime(1e-5,n+r),a.start(n),a.stop(n+r);}destroy(){this.audioContext&&(this.audioContext.close(),this.audioContext=null);}};var m="CF7JaA.MUFxFA:-9iIuCIf6yhXp5SV31j4Pvy8SreELcO_04ifYgleK5E",c=null,g=class{constructor(e){this.ably=null;this.channel=null;this.connected=false;if(!e.publicKey)throw new u("Public key is required");if(!e.userId)throw new u("User ID is required");this.config=e,this.sound=new y,this.connect();}connect(){return h(this,null,function*(){if(!this.connected)try{let{default:e}=yield import('ably');this.ably=new e.Realtime({key:m,logLevel:0});let n=`project:${this.config.publicKey}:user:${this.config.userId}`;this.channel=this.ably.channels.get(n),this.channel.subscribe("notification",o=>{this.handleNotification(o.data);}),this.ably.connection.on("connected",()=>{this.connected=!0;}),this.ably.connection.on("disconnected",()=>{this.connected=!1;}),this.ably.connection.on("failed",()=>{this.connected=!1;});}catch(e){throw new u(`Failed to connect: ${e}`)}})}disconnect(){this.channel&&(this.channel.unsubscribe(),this.channel=null),this.ably&&(this.ably.close(),this.ably=null,this.connected=false);}destroy(){this.disconnect(),this.sound.destroy(),c===this&&(c=null);}handleNotification(e){try{let n=e.channels||{sound:!0,push:!1};n.sound&&this.sound.play(),n.push&&(document.hidden||!document.hasFocus())&&this.handlePushNotification(e);}catch(n){}}handlePushNotification(e){if("Notification"in window)if(Notification.permission==="granted")try{let n=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| import x from'pusher-js';var v=(o,e,n)=>new Promise((s,r)=>{var p=i=>{try{t(n.next(i));}catch(u){r(u);}},c=i=>{try{t(n.throw(i));}catch(u){r(u);}},t=i=>i.done?s(i.value):Promise.resolve(i.value).then(p,c);t((n=n.apply(o,e)).next());});var l=class extends Error{constructor(e){super(e),this.name="NotifyOnWebError";}};var f=class{constructor(){this.audioContext=null;}play(){return v(this,null,function*(){try{this.audioContext||(this.audioContext=new(window.AudioContext||window.webkitAudioContext));let e=this.audioContext.currentTime;this.playTone(349,e,.1,.18,!0),this.playTone(440,e+.08,.08,.22,!0),this.playTone(523,e+.15,.04,.15,!1);}catch(e){}})}playTone(e,n,s,r,p=false){if(!this.audioContext)return;let c=this.audioContext.createOscillator(),t=this.audioContext.createGain(),i=this.audioContext.createDelay(.5);i.delayTime.value=.02;let u=this.audioContext.createGain();u.gain.value=.15;let d=this.audioContext.createBiquadFilter();if(d.type="lowpass",d.frequency.value=1800,d.Q.value=1.2,c.connect(d),d.connect(t),t.connect(this.audioContext.destination),t.connect(i),i.connect(u),u.connect(this.audioContext.destination),c.type="sine",c.frequency.value=e,p){let h=this.audioContext.createOscillator(),y=this.audioContext.createGain();h.frequency.value=5,y.gain.value=2,h.connect(y),y.connect(c.frequency),h.start(n),h.stop(n+r);}t.gain.setValueAtTime(1e-5,n),t.gain.exponentialRampToValueAtTime(s,n+.01),t.gain.setValueAtTime(s*.9,n+r-.06),t.gain.exponentialRampToValueAtTime(1e-5,n+r),c.start(n),c.stop(n+r);}destroy(){this.audioContext&&(this.audioContext.close(),this.audioContext=null);}};var g="70225167dde6bac400ea",m="mt1",a=null,b=class{constructor(e){this.pusher=null;this.channel=null;this.connected=false;if(!e.publicKey)throw new l("Public key is required");if(!e.userId)throw new l("User ID is required");this.config=e,this.sound=new f,this.connect();}connect(){if(!this.connected)try{this.pusher=new x(g,{cluster:m});let e=`project-${this.config.publicKey}-user-${this.config.userId}`;this.channel=this.pusher.subscribe(e),this.channel.bind("notification",n=>{this.handleNotification(n);}),this.pusher.connection.bind("connected",()=>{this.connected=!0;}),this.pusher.connection.bind("disconnected",()=>{this.connected=!1;}),this.pusher.connection.bind("failed",()=>{this.connected=!1;});}catch(e){throw new l(`Failed to connect: ${e}`)}}disconnect(){this.channel&&this.pusher&&(this.pusher.unsubscribe(this.channel.name),this.channel=null),this.pusher&&(this.pusher.disconnect(),this.pusher=null,this.connected=false);}destroy(){this.disconnect(),this.sound.destroy(),a===this&&(a=null);}handleNotification(e){try{let n=e.channels||{sound:!0,push:!1};n.sound&&this.sound.play(),n.push&&(document.hidden||!document.hasFocus())&&this.handlePushNotification(e);}catch(n){}}handlePushNotification(e){if("Notification"in window)if(Notification.permission==="granted")try{let n=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:!1,silent:!1});n.onclick=()=>{window.focus(),n.close();};}catch(n){}else Notification.permission!=="denied"&&Notification.requestPermission().then(n=>{if(n==="granted"){let o=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:!1,silent:!1});n.onclick=()=>{window.focus(),n.close();};}catch(n){}else Notification.permission!=="denied"&&Notification.requestPermission().then(n=>{if(n==="granted"){let s=new Notification("Task Complete",{body:`${e.message||"Your task has finished running"} | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:false,silent:false});o.onclick=()=>{window.focus(),o.close();};}});}};function v(s){if(c){let e=c.config;if(e.publicKey===s.publicKey&&e.userId===s.userId)return c;c.destroy();}return c=new g(s),c}export{v as connect};//# sourceMappingURL=index.mjs.map | ||
| Powered by NotifyOn`,icon:"/favicon.ico",tag:e.id,requireInteraction:false,silent:false});s.onclick=()=>{window.focus(),s.close();};}});}};function C(o){if(a){let e=a.config;if(e.publicKey===o.publicKey&&e.userId===o.userId)return a;a.destroy();}return a=new b(o),a}export{C as connect};//# sourceMappingURL=index.mjs.map | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/types.ts","../src/sound.ts","../src/client.ts"],"names":["NotifyOnWebError","message","NotificationSound","__async","now","error","frequency","startTime","volume","duration","addVibrato","oscillator","gainNode","delay","delayGain","filter","vibrato","vibratoGain","ABLY_SUBSCRIBE_KEY","globalInstance","NotifyOnWeb","config","AblyLib","channelName","data","channels","notification","permission","connect","existingConfig"],"mappings":"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","file":"index.mjs","sourcesContent":["export interface NotifyOnWebConfig {\n publicKey: string; // NotifyOn project public key\n userId: string;\n}\n\nexport interface NotificationEvent {\n id: string;\n message: string;\n timestamp: string;\n channels?: {\n sound?: boolean;\n push?: boolean;\n };\n}\n\nexport class NotifyOnWebError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NotifyOnWebError';\n }\n}","export class NotificationSound {\n private audioContext: AudioContext | null = null;\n\n /**\n * Play a notification sound using Web Audio API\n */\n async play(): Promise<void> {\n try {\n // Create audio context if not exists\n if (!this.audioContext) {\n this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n\n const now = this.audioContext.currentTime;\n \n // Create a natural, pleasant chime with mid-range tones\n // First tone - F4 (349Hz) with slight vibrato for naturalness\n this.playTone(349, now, 0.1, 0.18, true);\n \n // Second tone - A4 (440Hz) - major third up, very harmonious\n this.playTone(440, now + 0.08, 0.08, 0.22, true);\n \n // Third soft tone - C5 (523Hz) - adds sparkle\n this.playTone(523, now + 0.15, 0.04, 0.15, false);\n \n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private playTone(frequency: number, startTime: number, volume: number, duration: number, addVibrato: boolean = false): void {\n if (!this.audioContext) return;\n \n // Create oscillator and nodes for this tone\n const oscillator = this.audioContext.createOscillator();\n const gainNode = this.audioContext.createGain();\n \n // Add reverb-like effect with delay for warmth\n const delay = this.audioContext.createDelay(0.5);\n delay.delayTime.value = 0.02;\n \n const delayGain = this.audioContext.createGain();\n delayGain.gain.value = 0.15; // Very subtle reverb\n \n // Add a low-pass filter with some resonance for character\n const filter = this.audioContext.createBiquadFilter();\n filter.type = 'lowpass';\n filter.frequency.value = 1800; // Balanced cutoff\n filter.Q.value = 1.2; // Slight resonance for character\n \n // Connect main signal path: oscillator -> filter -> gain -> destination\n oscillator.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(this.audioContext.destination);\n \n // Connect delay path for subtle reverb: gain -> delay -> delayGain -> destination\n gainNode.connect(delay);\n delay.connect(delayGain);\n delayGain.connect(this.audioContext.destination);\n \n // Use sine wave for the most gentle sound\n oscillator.type = 'sine';\n oscillator.frequency.value = frequency;\n \n // Add subtle vibrato for more natural, less mechanical sound\n if (addVibrato) {\n const vibrato = this.audioContext.createOscillator();\n const vibratoGain = this.audioContext.createGain();\n vibrato.frequency.value = 5; // 5Hz vibrato rate\n vibratoGain.gain.value = 2; // Very subtle frequency modulation\n vibrato.connect(vibratoGain);\n vibratoGain.connect(oscillator.frequency);\n vibrato.start(startTime);\n vibrato.stop(startTime + duration);\n }\n \n // Natural envelope with varied attack for organic feel\n gainNode.gain.setValueAtTime(0.00001, startTime);\n gainNode.gain.exponentialRampToValueAtTime(volume, startTime + 0.01); // Quick but soft attack\n gainNode.gain.setValueAtTime(volume * 0.9, startTime + duration - 0.06); // Slight decay\n gainNode.gain.exponentialRampToValueAtTime(0.00001, startTime + duration); // Natural release\n \n // Play the tone\n oscillator.start(startTime);\n oscillator.stop(startTime + duration);\n }\n\n /**\n * Clean up audio context\n */\n destroy(): void {\n if (this.audioContext) {\n this.audioContext.close();\n this.audioContext = null;\n }\n }\n}","import type { NotifyOnWebConfig, NotificationEvent } from \"./types\";\nimport { NotifyOnWebError } from \"./types\";\nimport { NotificationSound } from \"./sound\";\n\n// Type-only import for TypeScript\nimport type Ably from \"ably\";\n\nconst ABLY_SUBSCRIBE_KEY =\n \"CF7JaA.MUFxFA:-9iIuCIf6yhXp5SV31j4Pvy8SreELcO_04ifYgleK5E\";\n\n// Singleton instance storage\nlet globalInstance: NotifyOnWeb | null = null;\n\nexport class NotifyOnWeb {\n public readonly config: NotifyOnWebConfig;\n private ably: Ably.Realtime | null = null;\n private channel: Ably.RealtimeChannel | null = null;\n private sound: NotificationSound;\n private connected = false;\n\n constructor(config: NotifyOnWebConfig) {\n if (!config.publicKey) {\n throw new NotifyOnWebError(\"Public key is required\");\n }\n if (!config.userId) {\n throw new NotifyOnWebError(\"User ID is required\");\n }\n\n this.config = config;\n\n this.sound = new NotificationSound();\n\n // Auto-connect on instantiation\n this.connect();\n }\n\n /**\n * Start listening for notifications (called automatically)\n */\n private async connect(): Promise<void> {\n if (this.connected) {\n return;\n }\n\n try {\n // Dynamically import Ably only on client side\n const { default: AblyLib } = await import(\"ably\");\n \n // Initialize Ably with our internal subscribe key\n this.ably = new AblyLib.Realtime({\n key: ABLY_SUBSCRIBE_KEY,\n // Optional: log level for debugging\n logLevel: 0, // 0 = verbose, 4 = none\n });\n\n // Use last 8 characters of public key for channel naming\n // This provides enough uniqueness while keeping channel names manageable\n const channelName = `project:${this.config.publicKey}:user:${this.config.userId}`;\n this.channel = this.ably.channels.get(channelName);\n\n // Subscribe to notification messages\n this.channel.subscribe(\"notification\", (message) => {\n this.handleNotification(message.data);\n });\n\n // Monitor connection state\n this.ably.connection.on(\"connected\", () => {\n this.connected = true;\n });\n\n this.ably.connection.on(\"disconnected\", () => {\n this.connected = false;\n });\n\n this.ably.connection.on(\"failed\", () => {\n this.connected = false;\n });\n } catch (error) {\n throw new NotifyOnWebError(`Failed to connect: ${error}`);\n }\n }\n\n /**\n * Stop listening for notifications (called automatically on destroy)\n */\n private disconnect(): void {\n if (this.channel) {\n this.channel.unsubscribe();\n this.channel = null;\n }\n\n if (this.ably) {\n this.ably.close();\n this.ably = null;\n this.connected = false;\n }\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this.disconnect();\n this.sound.destroy();\n // Clear global instance if this is the one being destroyed\n if (globalInstance === this) {\n globalInstance = null;\n }\n }\n\n private handleNotification(data: NotificationEvent): void {\n try {\n // Check which channels are enabled for this notification\n const channels = data.channels || { sound: true, push: false }; // Default to sound enabled\n\n // Progressive notification strategy:\n // 1. Always play sound if enabled\n // 2. Show push only if enabled AND tab is not visible\n\n // Handle sound notification (always if enabled)\n if (channels.sound) {\n this.sound.play();\n }\n\n // Handle push notification (only if tab is not visible/focused)\n if (channels.push) {\n // Check if document is hidden or not focused\n const isTabHidden = document.hidden || !document.hasFocus();\n\n if (isTabHidden) {\n this.handlePushNotification(data);\n }\n }\n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private handlePushNotification(data: NotificationEvent): void {\n // Check if browser supports notifications\n if (!(\"Notification\" in window)) {\n return;\n }\n\n // Check notification permission\n if (Notification.permission === \"granted\") {\n // Create push notification\n try {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n } catch (error) {\n // Silently handle notification creation errors\n }\n } else if (Notification.permission !== \"denied\") {\n // Request permission if not yet decided\n Notification.requestPermission().then((permission) => {\n if (permission === \"granted\") {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n }\n });\n }\n }\n}\n\n// Simple function API with singleton pattern\nexport function connect(config: NotifyOnWebConfig): NotifyOnWeb {\n // Check if we already have a connection with the same config\n if (globalInstance) {\n const existingConfig = globalInstance.config;\n // If connecting with same publicKey and userId, return existing instance\n if (\n existingConfig.publicKey === config.publicKey &&\n existingConfig.userId === config.userId\n ) {\n return globalInstance;\n }\n // If different config, destroy old connection and create new one\n globalInstance.destroy();\n }\n \n // Create and store new instance\n globalInstance = new NotifyOnWeb(config);\n return globalInstance;\n}\n"]} | ||
| {"version":3,"sources":["../src/types.ts","../src/sound.ts","../src/client.ts"],"names":["NotifyOnWebError","message","NotificationSound","__async","now","error","frequency","startTime","volume","duration","addVibrato","oscillator","gainNode","delay","delayGain","filter","vibrato","vibratoGain","PUSHER_APP_KEY","PUSHER_APP_CLUSTER","globalInstance","NotifyOnWeb","config","Pusher","channelName","data","channels","notification","permission","connect","existingConfig"],"mappings":"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","file":"index.mjs","sourcesContent":["export interface NotifyOnWebConfig {\n publicKey: string; // NotifyOn project public key\n userId: string;\n}\n\nexport interface NotificationEvent {\n id: string;\n message: string;\n timestamp: string;\n channels?: {\n sound?: boolean;\n push?: boolean;\n };\n}\n\nexport class NotifyOnWebError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NotifyOnWebError\";\n }\n}\n","export class NotificationSound {\n private audioContext: AudioContext | null = null;\n\n /**\n * Play a notification sound using Web Audio API\n */\n async play(): Promise<void> {\n try {\n // Create audio context if not exists\n if (!this.audioContext) {\n this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n\n const now = this.audioContext.currentTime;\n \n // Create a natural, pleasant chime with mid-range tones\n // First tone - F4 (349Hz) with slight vibrato for naturalness\n this.playTone(349, now, 0.1, 0.18, true);\n \n // Second tone - A4 (440Hz) - major third up, very harmonious\n this.playTone(440, now + 0.08, 0.08, 0.22, true);\n \n // Third soft tone - C5 (523Hz) - adds sparkle\n this.playTone(523, now + 0.15, 0.04, 0.15, false);\n \n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private playTone(frequency: number, startTime: number, volume: number, duration: number, addVibrato: boolean = false): void {\n if (!this.audioContext) return;\n \n // Create oscillator and nodes for this tone\n const oscillator = this.audioContext.createOscillator();\n const gainNode = this.audioContext.createGain();\n \n // Add reverb-like effect with delay for warmth\n const delay = this.audioContext.createDelay(0.5);\n delay.delayTime.value = 0.02;\n \n const delayGain = this.audioContext.createGain();\n delayGain.gain.value = 0.15; // Very subtle reverb\n \n // Add a low-pass filter with some resonance for character\n const filter = this.audioContext.createBiquadFilter();\n filter.type = 'lowpass';\n filter.frequency.value = 1800; // Balanced cutoff\n filter.Q.value = 1.2; // Slight resonance for character\n \n // Connect main signal path: oscillator -> filter -> gain -> destination\n oscillator.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(this.audioContext.destination);\n \n // Connect delay path for subtle reverb: gain -> delay -> delayGain -> destination\n gainNode.connect(delay);\n delay.connect(delayGain);\n delayGain.connect(this.audioContext.destination);\n \n // Use sine wave for the most gentle sound\n oscillator.type = 'sine';\n oscillator.frequency.value = frequency;\n \n // Add subtle vibrato for more natural, less mechanical sound\n if (addVibrato) {\n const vibrato = this.audioContext.createOscillator();\n const vibratoGain = this.audioContext.createGain();\n vibrato.frequency.value = 5; // 5Hz vibrato rate\n vibratoGain.gain.value = 2; // Very subtle frequency modulation\n vibrato.connect(vibratoGain);\n vibratoGain.connect(oscillator.frequency);\n vibrato.start(startTime);\n vibrato.stop(startTime + duration);\n }\n \n // Natural envelope with varied attack for organic feel\n gainNode.gain.setValueAtTime(0.00001, startTime);\n gainNode.gain.exponentialRampToValueAtTime(volume, startTime + 0.01); // Quick but soft attack\n gainNode.gain.setValueAtTime(volume * 0.9, startTime + duration - 0.06); // Slight decay\n gainNode.gain.exponentialRampToValueAtTime(0.00001, startTime + duration); // Natural release\n \n // Play the tone\n oscillator.start(startTime);\n oscillator.stop(startTime + duration);\n }\n\n /**\n * Clean up audio context\n */\n destroy(): void {\n if (this.audioContext) {\n this.audioContext.close();\n this.audioContext = null;\n }\n }\n}","import type { NotifyOnWebConfig, NotificationEvent } from \"./types\";\nimport { NotifyOnWebError } from \"./types\";\nimport { NotificationSound } from \"./sound\";\nimport Pusher from \"pusher-js\";\n\nconst PUSHER_APP_KEY = \"70225167dde6bac400ea\";\nconst PUSHER_APP_CLUSTER = \"mt1\";\n\n// Singleton instance storage\nlet globalInstance: NotifyOnWeb | null = null;\n\nexport class NotifyOnWeb {\n public readonly config: NotifyOnWebConfig;\n private pusher: Pusher | null = null;\n private channel: any | null = null;\n private sound: NotificationSound;\n private connected = false;\n\n constructor(config: NotifyOnWebConfig) {\n if (!config.publicKey) {\n throw new NotifyOnWebError(\"Public key is required\");\n }\n if (!config.userId) {\n throw new NotifyOnWebError(\"User ID is required\");\n }\n\n this.config = config;\n\n this.sound = new NotificationSound();\n\n // Auto-connect on instantiation\n this.connect();\n }\n\n /**\n * Start listening for notifications (called automatically)\n */\n private connect(): void {\n if (this.connected) {\n return;\n }\n\n try {\n this.pusher = new Pusher(PUSHER_APP_KEY, {\n cluster: PUSHER_APP_CLUSTER,\n });\n\n // Channel naming convention matches server\n const channelName = `project-${this.config.publicKey}-user-${this.config.userId}`;\n this.channel = this.pusher.subscribe(channelName);\n\n // Subscribe to notification events\n this.channel.bind(\"notification\", (data: any) => {\n this.handleNotification(data);\n });\n\n // Monitor connection state\n this.pusher.connection.bind(\"connected\", () => {\n this.connected = true;\n });\n\n this.pusher.connection.bind(\"disconnected\", () => {\n this.connected = false;\n });\n\n this.pusher.connection.bind(\"failed\", () => {\n this.connected = false;\n });\n } catch (error) {\n throw new NotifyOnWebError(`Failed to connect: ${error}`);\n }\n }\n\n /**\n * Stop listening for notifications (called automatically on destroy)\n */\n private disconnect(): void {\n if (this.channel && this.pusher) {\n this.pusher.unsubscribe(this.channel.name);\n this.channel = null;\n }\n\n if (this.pusher) {\n this.pusher.disconnect();\n this.pusher = null;\n this.connected = false;\n }\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this.disconnect();\n this.sound.destroy();\n // Clear global instance if this is the one being destroyed\n if (globalInstance === this) {\n globalInstance = null;\n }\n }\n\n private handleNotification(data: NotificationEvent): void {\n try {\n // Check which channels are enabled for this notification\n const channels = data.channels || { sound: true, push: false }; // Default to sound enabled\n\n // Progressive notification strategy:\n // 1. Always play sound if enabled\n // 2. Show push only if enabled AND tab is not visible\n\n // Handle sound notification (always if enabled)\n if (channels.sound) {\n this.sound.play();\n }\n\n // Handle push notification (only if tab is not visible/focused)\n if (channels.push) {\n // Check if document is hidden or not focused\n const isTabHidden = document.hidden || !document.hasFocus();\n\n if (isTabHidden) {\n this.handlePushNotification(data);\n }\n }\n } catch (error) {\n // Silently handle errors to avoid polluting user's console\n }\n }\n\n private handlePushNotification(data: NotificationEvent): void {\n // Check if browser supports notifications\n if (!(\"Notification\" in window)) {\n return;\n }\n\n // Check notification permission\n if (Notification.permission === \"granted\") {\n // Create push notification\n try {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n } catch (error) {\n // Silently handle notification creation errors\n }\n } else if (Notification.permission !== \"denied\") {\n // Request permission if not yet decided\n Notification.requestPermission().then((permission) => {\n if (permission === \"granted\") {\n const notification = new Notification(\"Task Complete\", {\n body: `${\n data.message || \"Your task has finished running\"\n }\\n\\nPowered by NotifyOn`,\n icon: \"/favicon.ico\",\n tag: data.id,\n requireInteraction: false,\n silent: false,\n });\n\n // Handle click - focus the tab/window\n notification.onclick = () => {\n window.focus();\n notification.close();\n };\n }\n });\n }\n }\n}\n\n// Simple function API with singleton pattern\nexport function connect(config: NotifyOnWebConfig): NotifyOnWeb {\n // Check if we already have a connection with the same config\n if (globalInstance) {\n const existingConfig = globalInstance.config;\n // If connecting with same publicKey and userId, return existing instance\n if (\n existingConfig.publicKey === config.publicKey &&\n existingConfig.userId === config.userId\n ) {\n return globalInstance;\n }\n // If different config, destroy old connection and create new one\n globalInstance.destroy();\n }\n\n // Create and store new instance\n globalInstance = new NotifyOnWeb(config);\n return globalInstance;\n}\n"]} |
+2
-2
| { | ||
| "name": "@notifyon/web", | ||
| "version": "0.1.3", | ||
| "version": "0.1.4", | ||
| "description": "NotifyOn Web SDK", | ||
@@ -45,3 +45,3 @@ "publishConfig": { | ||
| "dependencies": { | ||
| "ably": "^2.12.0" | ||
| "pusher-js": "^8.4.0" | ||
| }, | ||
@@ -48,0 +48,0 @@ "scripts": { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2
-80%1
-50%454463
-58.57%339
-57.47%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed