api-ape 2.1.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +184 -195
  2. package/client/README.md +37 -30
  3. package/client/browser.js +4 -14
  4. package/client/connectSocket.js +167 -42
  5. package/client/index.js +171 -0
  6. package/client/transports/streaming.js +3 -16
  7. package/dist/ape.js +2 -1049
  8. package/dist/ape.js.map +7 -0
  9. package/dist/api-ape.min.js +2 -0
  10. package/dist/api-ape.min.js.map +7 -0
  11. package/index.d.ts +67 -23
  12. package/package.json +27 -8
  13. package/server/README.md +52 -11
  14. package/server/lib/broadcast.js +25 -8
  15. package/server/lib/bun.js +122 -0
  16. package/server/lib/longPolling.js +28 -23
  17. package/server/lib/main.js +372 -46
  18. package/server/lib/wiring.js +19 -12
  19. package/server/lib/ws/adapters/bun.js +225 -0
  20. package/server/lib/ws/adapters/deno.js +186 -0
  21. package/server/lib/ws/frames.js +217 -0
  22. package/server/lib/ws/index.js +15 -0
  23. package/server/lib/ws/server.js +109 -0
  24. package/server/lib/ws/socket.js +222 -0
  25. package/server/lib/wsProvider.js +135 -0
  26. package/server/socket/receive.js +14 -1
  27. package/server/socket/send.js +6 -6
  28. package/server/utils/parseUserAgent.js +286 -0
  29. package/example/Bun/README.md +0 -74
  30. package/example/Bun/api/message.ts +0 -11
  31. package/example/Bun/index.html +0 -76
  32. package/example/Bun/package.json +0 -9
  33. package/example/Bun/server.ts +0 -59
  34. package/example/Bun/styles.css +0 -128
  35. package/example/ExpressJs/README.md +0 -95
  36. package/example/ExpressJs/api/message.js +0 -11
  37. package/example/ExpressJs/backend.js +0 -39
  38. package/example/ExpressJs/index.html +0 -88
  39. package/example/ExpressJs/package-lock.json +0 -834
  40. package/example/ExpressJs/package.json +0 -10
  41. package/example/ExpressJs/styles.css +0 -128
  42. package/example/NextJs/.dockerignore +0 -29
  43. package/example/NextJs/Dockerfile +0 -52
  44. package/example/NextJs/Dockerfile.dev +0 -27
  45. package/example/NextJs/README.md +0 -113
  46. package/example/NextJs/ape/client.js +0 -66
  47. package/example/NextJs/ape/embed.js +0 -12
  48. package/example/NextJs/ape/index.js +0 -23
  49. package/example/NextJs/ape/logic/chat.js +0 -62
  50. package/example/NextJs/ape/onConnect.js +0 -69
  51. package/example/NextJs/ape/onDisconnect.js +0 -13
  52. package/example/NextJs/ape/onError.js +0 -9
  53. package/example/NextJs/ape/onReceive.js +0 -15
  54. package/example/NextJs/ape/onSend.js +0 -15
  55. package/example/NextJs/api/message.js +0 -44
  56. package/example/NextJs/docker-compose.yml +0 -22
  57. package/example/NextJs/next-env.d.ts +0 -5
  58. package/example/NextJs/next.config.js +0 -8
  59. package/example/NextJs/package-lock.json +0 -6400
  60. package/example/NextJs/package.json +0 -24
  61. package/example/NextJs/pages/Info.tsx +0 -153
  62. package/example/NextJs/pages/_app.tsx +0 -6
  63. package/example/NextJs/pages/index.tsx +0 -275
  64. package/example/NextJs/public/favicon.ico +0 -0
  65. package/example/NextJs/public/vercel.svg +0 -4
  66. package/example/NextJs/server.js +0 -36
  67. package/example/NextJs/styles/Chat.module.css +0 -448
  68. package/example/NextJs/styles/Home.module.css +0 -129
  69. package/example/NextJs/styles/globals.css +0 -26
  70. package/example/NextJs/tsconfig.json +0 -20
  71. package/example/README.md +0 -117
  72. package/example/Vite/README.md +0 -68
  73. package/example/Vite/ape/client.ts +0 -66
  74. package/example/Vite/ape/onConnect.ts +0 -52
  75. package/example/Vite/api/message.ts +0 -57
  76. package/example/Vite/index.html +0 -16
  77. package/example/Vite/package.json +0 -19
  78. package/example/Vite/server.ts +0 -62
  79. package/example/Vite/src/App.vue +0 -170
  80. package/example/Vite/src/components/Info.vue +0 -352
  81. package/example/Vite/src/main.ts +0 -5
  82. package/example/Vite/src/style.css +0 -200
  83. package/example/Vite/src/vite-env.d.ts +0 -7
  84. package/example/Vite/vite.config.ts +0 -20
  85. package/todo.md +0 -85
  86. package/utils/jss.test.js +0 -261
  87. package/utils/messageHash.test.js +0 -56
package/client/browser.js CHANGED
@@ -1,33 +1,23 @@
1
1
  import connectSocket from './connectSocket.js'
2
2
 
3
- // Auto-configure for current page
4
- const port = window.location.port || (window.location.protocol === 'https:' ? 443 : 80)
5
- connectSocket.configure({ port: parseInt(port, 10) })
6
-
7
3
  const { sender, setOnReciver, onConnectionChange, getTransport } = connectSocket()
8
4
  connectSocket.autoReconnect()
9
5
 
10
6
  // Global API - use defineProperty to bypass Proxy interception
11
- window.ape = sender
12
- Object.defineProperty(window.ape, 'on', {
7
+ window.api = sender
8
+ Object.defineProperty(window.api, 'on', {
13
9
  value: setOnReciver,
14
10
  writable: false,
15
11
  enumerable: false,
16
12
  configurable: false
17
13
  })
18
- Object.defineProperty(window.ape, 'onConnectionChange', {
14
+ Object.defineProperty(window.api, 'onConnectionChange', {
19
15
  value: onConnectionChange,
20
16
  writable: false,
21
17
  enumerable: false,
22
18
  configurable: false
23
19
  })
24
- Object.defineProperty(window.ape, 'configure', {
25
- value: connectSocket.configure,
26
- writable: false,
27
- enumerable: false,
28
- configurable: false
29
- })
30
- Object.defineProperty(window.ape, 'getTransport', {
20
+ Object.defineProperty(window.api, 'getTransport', {
31
21
  value: getTransport,
32
22
  writable: false,
33
23
  enumerable: false,
@@ -1,19 +1,23 @@
1
1
  import messageHash from '../utils/messageHash'
2
2
  import jss from '../utils/jss'
3
- import { createStreamingTransport, configure as configureStreaming } from './transports/streaming'
3
+ import { createStreamingTransport } from './transports/streaming'
4
4
 
5
5
  let connect;
6
6
 
7
7
  // Connection state enum
8
8
  const ConnectionState = {
9
+ Offline: 'offline', // navigator.onLine = false
10
+ Walled: 'walled', // Captive portal detected (ping failed)
9
11
  Disconnected: 'disconnected',
10
12
  Connecting: 'connecting',
11
13
  Connected: 'connected',
12
14
  Closing: 'closing'
13
15
  }
14
16
 
15
- // Connection state tracking
16
- let connectionState = ConnectionState.Disconnected
17
+ // Connection state tracking - start with offline check
18
+ let connectionState = (typeof navigator !== 'undefined' && !navigator.onLine)
19
+ ? ConnectionState.Offline
20
+ : ConnectionState.Disconnected
17
21
  const connectionChangeListeners = []
18
22
 
19
23
  function notifyConnectionChange(newState) {
@@ -24,50 +28,129 @@ function notifyConnectionChange(newState) {
24
28
  }
25
29
 
26
30
  // Configuration
27
- let configuredPort = null
28
- let configuredHost = null
29
31
  let configuredTransport = 'auto' // 'auto' | 'websocket' | 'polling'
30
32
 
31
33
  // Transport state
32
34
  let currentTransport = null // 'websocket' | 'polling'
33
35
  let streamingTransport = null
34
36
  let wsRetryTimer = null
37
+ let networkCheckTimer = null
35
38
  const WS_FALLBACK_TIMEOUT = 4000 // Time to wait for WS before fallback
36
39
  const WS_RETRY_INTERVAL = 30000 // Retry WebSocket while in polling mode
40
+ const PING_TIMEOUT = 3000 // Timeout for ping check
41
+ const MAX_PING_CLOCK_SKEW = 60000 // Max allowed time difference (60s)
37
42
 
38
43
  /**
39
- * Configure api-ape client connection
40
- * @param {object} opts
41
- * @param {number} [opts.port] - WebSocket port (default: 9010 for local, 443/80 for remote)
42
- * @param {string} [opts.host] - WebSocket host (default: auto-detect from window.location)
43
- * @param {string} [opts.transport] - Transport mode: 'auto' | 'websocket' | 'polling'
44
+ * Check if running in dev/local mode
44
45
  */
45
- function configure(opts = {}) {
46
- if (opts.port) {
47
- configuredPort = opts.port
48
- configureStreaming({ port: opts.port })
49
- }
50
- if (opts.host) {
51
- configuredHost = opts.host
52
- configureStreaming({ host: opts.host })
53
- }
54
- if (opts.transport) {
55
- configuredTransport = opts.transport
46
+ function isDevMode() {
47
+ if (typeof window === 'undefined') return false
48
+ return ['localhost', '127.0.0.1', '[::1]'].includes(window.location.hostname)
49
+ }
50
+
51
+ /**
52
+ * Build ping URL for captive portal detection
53
+ */
54
+ function getPingUrl() {
55
+ const hostname = window.location.hostname
56
+ const localServers = ['localhost', '127.0.0.1', '[::1]']
57
+ const isLocal = localServers.includes(hostname)
58
+ const isHttps = window.location.protocol === 'https:'
59
+ const port = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
60
+ const protocol = isHttps ? 'https' : 'http'
61
+ const portSuffix = (isLocal || (port !== 80 && port !== 443)) ? `:${port}` : ''
62
+ return `${protocol}://${hostname}${portSuffix}/api/ape/ping`
63
+ }
64
+
65
+ /**
66
+ * Check for captive portal by pinging /api/ape/ping
67
+ * Returns 'ok' if real internet, 'walled' if captive portal detected
68
+ */
69
+ async function checkCaptivePortal() {
70
+ try {
71
+ const controller = new AbortController()
72
+ const timeoutId = setTimeout(() => controller.abort(), PING_TIMEOUT)
73
+
74
+ const response = await fetch(getPingUrl(), {
75
+ cache: 'no-store',
76
+ signal: controller.signal
77
+ })
78
+ clearTimeout(timeoutId)
79
+
80
+ if (!response.ok) {
81
+ if (isDevMode()) {
82
+ console.error('🦍 [DEV] Ping failed: HTTP', response.status)
83
+ }
84
+ return 'walled'
85
+ }
86
+
87
+ const data = await response.json()
88
+
89
+ // Verify response is genuine (not a captive portal redirect page)
90
+ if (data?.ok !== true) {
91
+ if (isDevMode()) {
92
+ console.error('🦍 [DEV] Ping failed: invalid response', data)
93
+ }
94
+ return 'walled'
95
+ }
96
+
97
+ // Validate timestamp to detect proxy replay attacks
98
+ if (typeof data.ts === 'number') {
99
+ const now = Date.now()
100
+ const skew = Math.abs(now - data.ts)
101
+ if (skew > MAX_PING_CLOCK_SKEW) {
102
+ if (isDevMode()) {
103
+ console.error('🦍 [DEV] Ping failed: timestamp too old/stale (skew:', skew, 'ms)')
104
+ }
105
+ return 'walled'
106
+ }
107
+ }
108
+
109
+ return 'ok'
110
+ } catch (err) {
111
+ if (isDevMode()) {
112
+ console.error('🦍 [DEV] Ping failed:', err.message || err)
113
+ }
114
+ return 'walled'
56
115
  }
57
116
  }
58
117
 
118
+ /**
119
+ * Setup navigator.onLine event listeners
120
+ */
121
+ function setupOnlineListeners() {
122
+ if (typeof window === 'undefined') return
123
+
124
+ window.addEventListener('online', () => {
125
+ console.log('🦍 Browser went online, checking network...')
126
+ // Trigger reconnection attempt
127
+ attemptConnection()
128
+ })
129
+
130
+ window.addEventListener('offline', () => {
131
+ console.log('🦍 Browser went offline')
132
+ notifyConnectionChange(ConnectionState.Offline)
133
+ })
134
+ }
135
+
136
+ // Setup listeners on module load (browser only)
137
+ if (typeof window !== 'undefined') {
138
+ setupOnlineListeners()
139
+ }
140
+
141
+
142
+
59
143
  /**
60
144
  * Get WebSocket URL - auto-detects from window.location, keeps /api/ape path
61
145
  */
62
146
  function getSocketUrl() {
63
- const hostname = configuredHost || window.location.hostname
147
+ const hostname = window.location.hostname
64
148
  const localServers = ["localhost", "127.0.0.1", "[::1]"]
65
149
  const isLocal = localServers.includes(hostname)
66
150
  const isHttps = window.location.protocol === "https:"
67
151
 
68
152
  // Default port: 9010 for local dev, otherwise use window.location.port or implicit 443/80
69
- const defaultPort = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
70
- const port = configuredPort || defaultPort
153
+ const port = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
71
154
 
72
155
  // Build URL - keep /api/ape path
73
156
  const protocol = isHttps ? "wss" : "ws"
@@ -83,7 +166,7 @@ const totalRequestTimeout = 10000
83
166
 
84
167
  const joinKey = "/"
85
168
  // Properties accessed directly on `ape` that should NOT be intercepted
86
- const reservedKeys = new Set(['on', 'onConnectionChange', 'configure', 'getTransport'])
169
+ const reservedKeys = new Set(['on', 'onConnectionChange', 'getTransport'])
87
170
  const handler = {
88
171
  get(fn, key) {
89
172
  // Skip proxy interception for reserved keys - return actual property
@@ -387,7 +470,7 @@ function cleanLinkedKeys(obj) {
387
470
  /**
388
471
  * Fetch binary resources and hydrate data object
389
472
  */
390
- async function fetchLinkedResources(data, hostId) {
473
+ async function fetchLinkedResources(data, clientId) {
391
474
  const resources = findLinkedResources(data)
392
475
 
393
476
  if (resources.length === 0) {
@@ -398,11 +481,10 @@ async function fetchLinkedResources(data, hostId) {
398
481
 
399
482
  const cleanedData = cleanLinkedKeys(data)
400
483
 
401
- const hostname = configuredHost || window.location.hostname
484
+ const hostname = window.location.hostname
402
485
  const isLocal = ["localhost", "127.0.0.1", "[::1]"].includes(hostname)
403
486
  const isHttps = window.location.protocol === "https:"
404
- const defaultPort = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
405
- const port = configuredPort || defaultPort
487
+ const port = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
406
488
  const protocol = isHttps ? "https" : "http"
407
489
  const portSuffix = (isLocal || (port !== 80 && port !== 443)) ? `:${port}` : ""
408
490
  const baseUrl = `${protocol}://${hostname}${portSuffix}`
@@ -412,7 +494,7 @@ async function fetchLinkedResources(data, hostId) {
412
494
  const response = await fetch(`${baseUrl}/api/ape/data/${hash}`, {
413
495
  credentials: 'include',
414
496
  headers: {
415
- 'X-Ape-Host-Id': hostId || ''
497
+ 'X-Ape-Client-Id': clientId || ''
416
498
  }
417
499
  })
418
500
 
@@ -431,17 +513,46 @@ async function fetchLinkedResources(data, hostId) {
431
513
  return cleanedData
432
514
  }
433
515
 
434
- function connectSocket() {
435
- // Skip if already connected or connecting
436
- if (__socket && __socket.readyState !== WebSocket.CLOSED) {
437
- return buildClientInterface()
438
- }
439
- if (currentTransport === 'polling' && streamingTransport?.isConnected()) {
440
- return buildClientInterface()
516
+ /**
517
+ * Attempt to establish connection with network pre-checks
518
+ */
519
+ async function attemptConnection() {
520
+ // Check if browser is online
521
+ if (typeof navigator !== 'undefined' && !navigator.onLine) {
522
+ notifyConnectionChange(ConnectionState.Offline)
523
+ return
441
524
  }
442
525
 
526
+ // Perform captive portal check
443
527
  notifyConnectionChange(ConnectionState.Connecting)
528
+ const pingResult = await checkCaptivePortal()
444
529
 
530
+ if (pingResult === 'walled') {
531
+ notifyConnectionChange(ConnectionState.Walled)
532
+ // Retry network check periodically
533
+ scheduleNetworkRetry()
534
+ return
535
+ }
536
+
537
+ // Network is good, proceed with socket connection
538
+ proceedWithConnection()
539
+ }
540
+
541
+ /**
542
+ * Schedule a retry of network check (for walled/offline states)
543
+ */
544
+ function scheduleNetworkRetry() {
545
+ if (networkCheckTimer) return
546
+ networkCheckTimer = setTimeout(() => {
547
+ networkCheckTimer = null
548
+ attemptConnection()
549
+ }, WS_RETRY_INTERVAL)
550
+ }
551
+
552
+ /**
553
+ * Proceed with WebSocket/polling connection after network checks pass
554
+ */
555
+ function proceedWithConnection() {
445
556
  // Determine which transport to use
446
557
  if (configuredTransport === 'polling') {
447
558
  switchToStreaming()
@@ -449,6 +560,22 @@ function connectSocket() {
449
560
  // 'auto' or 'websocket' - try WebSocket first
450
561
  tryWebSocket(false)
451
562
  }
563
+ }
564
+
565
+ function connectSocket() {
566
+ // Skip if already connected or connecting
567
+ if (__socket && __socket.readyState !== WebSocket.CLOSED) {
568
+ return buildClientInterface()
569
+ }
570
+ if (currentTransport === 'polling' && streamingTransport?.isConnected()) {
571
+ return buildClientInterface()
572
+ }
573
+ if (connectionState === ConnectionState.Connecting) {
574
+ return buildClientInterface()
575
+ }
576
+
577
+ // Start connection with network pre-checks
578
+ attemptConnection()
452
579
 
453
580
  return buildClientInterface()
454
581
  }
@@ -547,11 +674,10 @@ async function uploadBinaryData(queryId, uploads) {
547
674
  if (uploads.length === 0) return
548
675
 
549
676
  // Build base URL
550
- const hostname = configuredHost || window.location.hostname
677
+ const hostname = window.location.hostname
551
678
  const isLocal = ["localhost", "127.0.0.1", "[::1]"].includes(hostname)
552
679
  const isHttps = window.location.protocol === "https:"
553
- const defaultPort = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
554
- const port = configuredPort || defaultPort
680
+ const port = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
555
681
  const protocol = isHttps ? "https" : "http"
556
682
  const portSuffix = (isLocal || (port !== 80 && port !== 443)) ? `:${port}` : ""
557
683
  const baseUrl = `${protocol}://${hostname}${portSuffix}`
@@ -723,9 +849,8 @@ function buildClientInterface() {
723
849
  }
724
850
 
725
851
  connectSocket.autoReconnect = () => reconnect = true
726
- connectSocket.configure = configure
727
852
  connectSocket.ConnectionState = ConnectionState
728
853
  connect = connectSocket
729
854
 
730
855
  export default connect;
731
- export { configure, ConnectionState };
856
+ export { ConnectionState };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Unified api-ape export for browser
3
+ *
4
+ * Auto-detects browser environment, initializes client, and buffers
5
+ * calls until the connection is ready. No more getApeClient().then()!
6
+ *
7
+ * Usage:
8
+ * import api from 'api-ape'
9
+ *
10
+ * // Properties are proxied - calls buffer until connected
11
+ * api.message({ user: 'Bob', text: 'Hello!' })
12
+ *
13
+ * // Subscribe to broadcasts
14
+ * api.on('message', (data) => console.log(data))
15
+ *
16
+ * // Check connection state
17
+ * api.onConnectionChange((state) => console.log(state))
18
+ */
19
+
20
+ // Only run this in browser environments
21
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'
22
+
23
+ let clientPromise = null
24
+ let resolvedClient = null
25
+ const bufferedCalls = []
26
+ const bufferedReceivers = []
27
+ const connectionChangeHandlers = []
28
+ let currentConnectionState = 'disconnected'
29
+
30
+ /**
31
+ * Initialize the client (called once on first use)
32
+ */
33
+ function getClient() {
34
+ if (clientPromise) return clientPromise
35
+
36
+ if (!isBrowser) {
37
+ // Return a dummy object for SSR
38
+ return Promise.resolve(null)
39
+ }
40
+
41
+ clientPromise = (async () => {
42
+ const connectSocket = (await import('./connectSocket.js')).default
43
+
44
+ // Connect
45
+ const client = connectSocket()
46
+ connectSocket.autoReconnect()
47
+
48
+ // Track connection state
49
+ client.onConnectionChange((state) => {
50
+ currentConnectionState = state
51
+ connectionChangeHandlers.forEach(fn => fn(state))
52
+ })
53
+
54
+ resolvedClient = client
55
+
56
+ // Flush buffered receivers
57
+ bufferedReceivers.forEach(({ type, handler }) => {
58
+ client.setOnReciver(type, handler)
59
+ })
60
+ bufferedReceivers.length = 0
61
+
62
+ // Flush buffered calls
63
+ bufferedCalls.forEach(({ method, args, resolve, reject }) => {
64
+ try {
65
+ const result = client.sender[method](...args)
66
+ if (result && typeof result.then === 'function') {
67
+ result.then(resolve).catch(reject)
68
+ } else {
69
+ resolve(result)
70
+ }
71
+ } catch (err) {
72
+ reject(err)
73
+ }
74
+ })
75
+ bufferedCalls.length = 0
76
+
77
+ return client
78
+ })()
79
+
80
+ return clientPromise
81
+ }
82
+
83
+ /**
84
+ * Create a sender proxy that buffers calls until client is ready
85
+ */
86
+ const senderProxy = new Proxy({}, {
87
+ get(target, prop) {
88
+ // Reserved properties
89
+ if (prop === 'on') return on
90
+ if (prop === 'onConnectionChange') return onConnectionChange
91
+ if (prop === 'getTransport') return () => resolvedClient?.getTransport?.() || null
92
+ if (prop === 'then' || prop === 'catch') return undefined // Not a Promise
93
+
94
+ // Return a function that either calls directly or buffers
95
+ return (...args) => {
96
+ // If client is ready, call directly
97
+ if (resolvedClient) {
98
+ return resolvedClient.sender[prop](...args)
99
+ }
100
+
101
+ // Buffer the call and return a Promise
102
+ return new Promise((resolve, reject) => {
103
+ bufferedCalls.push({ method: prop, args, resolve, reject })
104
+ // Ensure client is initializing
105
+ getClient()
106
+ })
107
+ }
108
+ }
109
+ })
110
+
111
+ /**
112
+ * Subscribe to broadcasts from the server
113
+ * @param {string} type - Broadcast type to listen for
114
+ * @param {Function} handler - Handler function
115
+ */
116
+ function on(type, handler) {
117
+ if (resolvedClient) {
118
+ resolvedClient.setOnReciver(type, handler)
119
+ } else {
120
+ bufferedReceivers.push({ type, handler })
121
+ getClient()
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Subscribe to connection state changes
127
+ * @param {Function} handler - Called with state: 'offline' | 'walled' | 'disconnected' | 'connecting' | 'connected'
128
+ * @returns {Function} Unsubscribe function
129
+ */
130
+ function onConnectionChange(handler) {
131
+ connectionChangeHandlers.push(handler)
132
+ // Immediately call with current state
133
+ handler(currentConnectionState)
134
+
135
+ // If client exists, also register with it
136
+ if (resolvedClient) {
137
+ return resolvedClient.onConnectionChange(handler)
138
+ }
139
+
140
+ // Ensure client is initializing
141
+ getClient()
142
+
143
+ // Return unsubscribe function
144
+ return () => {
145
+ const idx = connectionChangeHandlers.indexOf(handler)
146
+ if (idx > -1) connectionChangeHandlers.splice(idx, 1)
147
+ }
148
+ }
149
+
150
+ // Define properties on the proxy to avoid Proxy interception issues
151
+ Object.defineProperty(senderProxy, 'on', {
152
+ value: on,
153
+ writable: false,
154
+ enumerable: false,
155
+ configurable: false
156
+ })
157
+
158
+ Object.defineProperty(senderProxy, 'onConnectionChange', {
159
+ value: onConnectionChange,
160
+ writable: false,
161
+ enumerable: false,
162
+ configurable: false
163
+ })
164
+
165
+ // Auto-initialize in browser
166
+ if (isBrowser) {
167
+ getClient()
168
+ }
169
+
170
+ export default senderProxy
171
+ export { on, onConnectionChange, getClient }
@@ -5,29 +5,16 @@ import jss from '../../utils/jss'
5
5
  * Uses fetch + ReadableStream for receiving, POST for sending
6
6
  */
7
7
 
8
- // Configuration
9
- let configuredPort = null
10
- let configuredHost = null
11
-
12
- /**
13
- * Configure transport connection options
14
- */
15
- function configure(opts = {}) {
16
- if (opts.port) configuredPort = opts.port
17
- if (opts.host) configuredHost = opts.host
18
- }
19
-
20
8
  /**
21
9
  * Get base URL for polling endpoints
22
10
  */
23
11
  function getPollUrl() {
24
- const hostname = configuredHost || window.location.hostname
12
+ const hostname = window.location.hostname
25
13
  const localServers = ["localhost", "127.0.0.1", "[::1]"]
26
14
  const isLocal = localServers.includes(hostname)
27
15
  const isHttps = window.location.protocol === "https:"
28
16
 
29
- const defaultPort = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
30
- const port = configuredPort || defaultPort
17
+ const port = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
31
18
 
32
19
  const protocol = isHttps ? "https" : "http"
33
20
  const portSuffix = (isLocal || (port !== 80 && port !== 443)) ? `:${port}` : ""
@@ -250,4 +237,4 @@ function createStreamingTransport() {
250
237
  }
251
238
  }
252
239
 
253
- export { createStreamingTransport, configure, getPollUrl }
240
+ export { createStreamingTransport, getPollUrl }