webview.io 1.0.5 → 1.1.0

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/README.md CHANGED
@@ -106,9 +106,11 @@ function MapComponent() {
106
106
 
107
107
  ```javascript
108
108
  // In your HTML/JS loaded in the WebView
109
+ // The bridge is automatically available as window._wio after injection
109
110
 
110
- // The bridge is automatically available as window._wio
111
+ // IMPORTANT: You must manually call listen() to start the connection
111
112
  const wio = window._wio
113
+ wio.listen() // Required to initiate connection handshake
112
114
 
113
115
  // Handle connection
114
116
  wio.on('connect', () => {
@@ -140,7 +142,9 @@ const wio = new WIO({
140
142
  maxMessageSize: 1024 * 1024, // Max message size in bytes (1MB)
141
143
  maxMessagesPerSecond: 100, // Rate limit (100 messages/second)
142
144
  autoReconnect: true, // Enable automatic reconnection
143
- messageQueueSize: 50 // Max queued messages when disconnected
145
+ messageQueueSize: 50, // Max queued messages when disconnected
146
+ allowedIncomingEvents: ['hello', 'response'], // Optional incoming event allowlist (non-reserved events)
147
+ validateIncoming: (event, payload) => true // Optional custom incoming validator
144
148
  })
145
149
  ```
146
150
 
@@ -180,12 +184,28 @@ This prevents:
180
184
  - Fires `connect_timeout` event if all attempts fail
181
185
 
182
186
  **EMBEDDED Side:**
183
- - Automatically announces `__embedded_ready` when loaded
187
+ - Bridge is available as `window._wio` after injection
188
+ - **Must manually call `window._wio.listen()` to start connection**
189
+ - Automatically announces `__embedded_ready` when `listen()` is called
184
190
  - Retries announcement every 2 seconds until connected
185
191
  - Handles race condition if loaded before React Native
186
192
  - Responds to `ping` with `pong` containing same token
187
193
  - Confirms connection upon receiving `__connection_ack`
188
194
 
195
+ ## Reserved Events
196
+
197
+ The library uses several internal events for connection management. These events are handled automatically and **bypass normal message queuing** to ensure reliable connection establishment:
198
+
199
+ - **`ping`** - Initial connection request from WEBVIEW
200
+ - **`pong`** - Connection response from EMBEDDED
201
+ - **`__heartbeat`** - Periodic health check request
202
+ - **`__heartbeat_response`** - Health check response
203
+ - **`__embedded_ready`** - EMBEDDED announces it's ready to connect
204
+ - **`__connection_ack`** - Final handshake acknowledgment from WEBVIEW
205
+ - **`__webview_ready`** - Signal that WEBVIEW peer is ready (currently logged but not actively used)
206
+
207
+ **Important:** These events are sent immediately even when disconnected, unlike regular events which are queued. Do not use these event names for your application logic.
208
+
189
209
  ## Async/Await Support
190
210
 
191
211
  ### Send Messages with Acknowledgments
@@ -231,7 +251,7 @@ console.log('User data received:', userData)
231
251
  ### Connection Timeout Handling
232
252
 
233
253
  ```javascript
234
- // Handle connection timeout (new event)
254
+ // Handle connection timeout
235
255
  wio.on('connect_timeout', ({ attempts }) => {
236
256
  console.log(`Failed to connect after ${attempts} attempts`)
237
257
  // Optionally retry manually or show error to user
@@ -267,13 +287,13 @@ const stats = wio.getStats()
267
287
  console.log(stats)
268
288
  // {
269
289
  // connected: true,
270
- // embeddedReady: true, // NEW: EMBEDDED peer readiness state
290
+ // embeddedReady: true, // EMBEDDED peer readiness state
271
291
  // peerType: 'WEBVIEW',
272
292
  // origin: 'https://example.com',
273
293
  // lastHeartbeat: 1609459200000,
274
294
  // queuedMessages: 0,
275
295
  // reconnectAttempts: 0,
276
- // connectionAttempts: 0, // NEW: Current connection attempts
296
+ // connectionAttempts: 0, // Current connection attempts
277
297
  // activeListeners: 5,
278
298
  // messageRate: 2
279
299
  // }
@@ -288,7 +308,7 @@ Each connection uses a unique token for validation:
288
308
  ```javascript
289
309
  // Automatically generated and validated during handshake
290
310
  // Prevents accepting messages from previous connections
291
- // Tokens are only used for connection-related events
311
+ // Tokens are only used for reserved connection events
292
312
  ```
293
313
 
294
314
  ### Origin Validation
@@ -312,6 +332,29 @@ wio.emit('data', {
312
332
  })
313
333
  ```
314
334
 
335
+ ### Incoming Event Allowlist & Validation
336
+
337
+ For defense-in-depth, you can restrict which **application-level** events are accepted and/or validate incoming payloads. Reserved internal events (handshake/heartbeat/readiness) are always allowed.
338
+
339
+ ```javascript
340
+ const wio = new WIO({
341
+ type: 'WEBVIEW',
342
+ debug: true,
343
+ allowedIncomingEvents: ['get:location', 'location:picked'],
344
+ validateIncoming: (event, payload) => {
345
+ // Example: simple checks
346
+ if (event === 'location:picked') return payload && typeof payload.lat === 'number'
347
+ return true
348
+ }
349
+ })
350
+
351
+ wio.on('error', (error) => {
352
+ if (error.type === 'DISALLOWED_EVENT' || error.type === 'INVALID_MESSAGE') {
353
+ console.warn('Dropped incoming message:', error)
354
+ }
355
+ })
356
+ ```
357
+
315
358
  ### Rate Limiting
316
359
 
317
360
  ```javascript
@@ -356,6 +399,7 @@ wio.on('error', (error) => {
356
399
 
357
400
  ```javascript
358
401
  // Messages are automatically queued when disconnected
402
+ // Reserved events bypass the queue for reliable connection handling
359
403
  wio.emit('important-data', { data: 'This will be queued if disconnected' })
360
404
 
361
405
  // Clear queue manually if needed
@@ -382,7 +426,7 @@ console.log(`${stats.queuedMessages} messages queued`)
382
426
 
383
427
  #### Connection Methods
384
428
  - **`initiate(webViewRef, origin)`** - Establish connection with retry logic (WEBVIEW peer only)
385
- - **`listen(hostOrigin?)`** - Listen for connection with readiness announcement (EMBEDDED peer only)
429
+ - **`listen(hostOrigin?)`** - Start listening for connection (EMBEDDED peer only). The `hostOrigin` parameter is informational only and used for logging - no validation is performed.
386
430
  - **`handleMessage(event)`** - Handle incoming message from WebView
387
431
  - **`disconnect(callback?)`** - Disconnect and cleanup all resources
388
432
  - **`isConnected()`** - Check connection status
@@ -399,15 +443,19 @@ console.log(`${stats.queuedMessages} messages queued`)
399
443
  #### Connection Events
400
444
  - **`connect`** - Connection established (after 3-way handshake)
401
445
  - **`disconnect`** - Connection lost with reason
402
- - **`connect_timeout`** - Initial connection failed after all attempts (NEW)
446
+ - **`connect_timeout`** - Initial connection failed after all attempts
403
447
  - **`reconnecting`** - Reconnection attempt started
404
448
  - **`reconnection_failed`** - All reconnection attempts failed
405
449
 
406
- #### Internal Events (handled automatically)
450
+ #### Reserved Internal Events
451
+ These events are handled automatically by the library. Do not use these names for your application events:
452
+ - **`ping`** - Connection initiation from WEBVIEW
453
+ - **`pong`** - Connection response from EMBEDDED
407
454
  - **`__embedded_ready`** - EMBEDDED peer announces readiness
408
455
  - **`__connection_ack`** - Final acknowledgment in 3-way handshake
409
- - **`__heartbeat`** - Connection health check
456
+ - **`__heartbeat`** - Connection health check request
410
457
  - **`__heartbeat_response`** - Heartbeat response
458
+ - **`__webview_ready`** - WEBVIEW ready signal (informational)
411
459
 
412
460
  #### Error Events
413
461
  - **`error`** - Various error conditions with detailed error objects
@@ -575,6 +623,8 @@ const response = await wio.emitAsync<{ query: string }, ApiResponse>(
575
623
  | `MESSAGE_HANDLING_ERROR` | Error processing incoming message |
576
624
  | `EMIT_ERROR` | Error sending message |
577
625
  | `LISTENER_ERROR` | Error in event listener |
626
+ | `DISALLOWED_EVENT` | Incoming event rejected by `allowedIncomingEvents` |
627
+ | `INVALID_MESSAGE` | Incoming message rejected by `validateIncoming` |
578
628
  | `RATE_LIMIT_EXCEEDED` | Too many messages sent |
579
629
  | `NO_CONNECTION` | Attempted to send without connection |
580
630
 
@@ -600,12 +650,13 @@ const response = await wio.emitAsync<{ query: string }, ApiResponse>(
600
650
  **Symptoms:** `connect_timeout` event fires, connection never succeeds
601
651
 
602
652
  **Solutions:**
603
- 1. Check that `injectedJavaScript` is properly set on WebView
604
- 2. Verify `javaScriptEnabled={true}` is set
605
- 3. Check browser console for JavaScript errors in WebView
606
- 4. Ensure origin matches exactly (including protocol)
607
- 5. Increase `connectionTimeout` for slow networks
608
- 6. Check that WebView content loads successfully
653
+ 1. **Verify `listen()` is called** - The EMBEDDED side must manually call `window._wio.listen()` to start the connection
654
+ 2. Check that `injectedJavaScript` is properly set on WebView
655
+ 3. Verify `javaScriptEnabled={true}` is set
656
+ 4. Check browser console for JavaScript errors in WebView
657
+ 5. Ensure origin matches exactly (including protocol)
658
+ 6. Increase `connectionTimeout` for slow networks
659
+ 7. Check that WebView content loads successfully
609
660
 
610
661
  ### Messages Not Received
611
662
 
@@ -617,6 +668,7 @@ const response = await wio.emitAsync<{ query: string }, ApiResponse>(
617
668
  3. Look for errors in `error` event listener
618
669
  4. Check if rate limiting is being exceeded
619
670
  5. Verify message size is under `maxMessageSize`
671
+ 6. Ensure you're not using reserved event names
620
672
 
621
673
  ### Frequent Disconnections
622
674
 
@@ -637,7 +689,7 @@ const response = await wio.emitAsync<{ query: string }, ApiResponse>(
637
689
  - Already handled! The three-way handshake with readiness announcements prevents this
638
690
  - EMBEDDED announces readiness periodically until connected
639
691
  - WEBVIEW retries connection attempts automatically
640
- - No action needed from your side
692
+ - Just ensure `window._wio.listen()` is called in your WebView code
641
693
 
642
694
  ## Differences from iframe.io
643
695
 
@@ -647,6 +699,7 @@ const response = await wio.emitAsync<{ query: string }, ApiResponse>(
647
699
  - **Initialization**: Uses `RefObject<WebView>` instead of `Window` object
648
700
  - **Message Handling**: Requires explicit `handleMessage()` call in `onMessage` prop
649
701
  - **Injected Script**: Uses `getInjectedJavaScript()` to setup bridge in WebView
702
+ - **Manual Connection**: EMBEDDED side must call `listen()` to start connection
650
703
  - **No DOM Dependencies**: Works in React Native environment without DOM APIs
651
704
  - **Enhanced Handshake**: Three-way handshake with token validation for mobile reliability
652
705
  - **Connection Retry**: Built-in retry logic for spotty mobile connections
package/dist/index.d.ts CHANGED
@@ -14,6 +14,16 @@ export type Options = {
14
14
  messageQueueSize?: number;
15
15
  connectionPingInterval?: number;
16
16
  maxConnectionAttempts?: number;
17
+ /**
18
+ * Optional allowlist of incoming application-level events.
19
+ * Reserved internal events (ping/pong/heartbeat/handshake) are always allowed.
20
+ */
21
+ allowedIncomingEvents?: string[];
22
+ /**
23
+ * Optional custom validator for incoming messages.
24
+ * Return false to drop a message; an 'error' event will be emitted.
25
+ */
26
+ validateIncoming?: (event: string, payload: any) => boolean;
17
27
  };
18
28
  export interface RegisteredEvents {
19
29
  [index: string]: Listener[];
@@ -78,6 +88,9 @@ export default class WIO {
78
88
  initiate(webViewRef: RefObject<WebView>, origin: string): this;
79
89
  /**
80
90
  * Listening to connection from the WebView host
91
+ *
92
+ * NOTE: This is called manually from page code,
93
+ * not auto-initialized
81
94
  */
82
95
  listen(hostOrigin?: string): this;
83
96
  /**
@@ -115,6 +128,7 @@ export default class WIO {
115
128
  /**
116
129
  * Get injected JavaScript for WebView
117
130
  * Sets up the EMBEDDED side of the bridge
131
+ * NOTE: Does not auto-initialize - page must call window._wio.listen()
118
132
  */
119
133
  getInjectedJavaScript(): string;
120
134
  }
package/dist/index.js CHANGED
@@ -45,6 +45,22 @@ var ackId = function () {
45
45
  return "".concat(timestamp, "_").concat(random);
46
46
  };
47
47
  var generateToken = function () {
48
+ // Prefer cryptographically strong randomness when available
49
+ try {
50
+ var globalCrypto = (typeof crypto !== 'undefined'
51
+ ? crypto
52
+ : (typeof window !== 'undefined' && window.crypto)
53
+ || (typeof globalThis !== 'undefined' && globalThis.crypto));
54
+ if (globalCrypto && typeof globalCrypto.getRandomValues === 'function') {
55
+ var buffer = new Uint32Array(4);
56
+ globalCrypto.getRandomValues(buffer);
57
+ var randomPart = Array.from(buffer).map(function (n) { return n.toString(16); }).join('');
58
+ return "".concat(Date.now(), "_").concat(randomPart);
59
+ }
60
+ }
61
+ catch (_a) {
62
+ // Fall back to Math.random-based implementation below
63
+ }
48
64
  return "".concat(Date.now(), "_").concat(Math.random().toString(36).substring(2, 15));
49
65
  };
50
66
  var RESERVED_EVENTS = [
@@ -143,13 +159,9 @@ var WIO = /** @class */ (function () {
143
159
  _this.connectionAttempts = 0;
144
160
  _this.connectionToken = generateToken();
145
161
  // Re-initiate connection for WEBVIEW type
146
- if (_this.peer.type === 'WEBVIEW') {
147
- _this.startConnectionAttempt();
148
- }
162
+ _this.peer.type === 'WEBVIEW' && _this.startConnectionAttempt();
149
163
  // For EMBEDDED type, announce readiness
150
- if (_this.peer.type === 'EMBEDDED') {
151
- _this.announceEmbeddedReady();
152
- }
164
+ _this.peer.type === 'EMBEDDED' && _this.announceEmbeddedReady();
153
165
  // Set timeout for this reconnection attempt
154
166
  setTimeout(function () {
155
167
  if (_this.peer.connected)
@@ -181,9 +193,8 @@ var WIO = /** @class */ (function () {
181
193
  _this.debug("[".concat(_this.peer.type, "] Connection attempt ").concat(_this.connectionAttempts, "/").concat(_this.options.maxConnectionAttempts));
182
194
  _this.emit('ping', { token: _this.connectionToken });
183
195
  }
184
- else {
196
+ else
185
197
  _this.stopConnectionAttempt();
186
- }
187
198
  }, this.options.connectionPingInterval);
188
199
  // Set overall timeout
189
200
  this.connectionAttemptTimer = setTimeout(function () {
@@ -225,16 +236,15 @@ var WIO = /** @class */ (function () {
225
236
  _this.debug("[".concat(_this.peer.type, "] Ready announcement attempt ").concat(attempts, "/").concat(maxAttempts));
226
237
  _this.emit('__embedded_ready');
227
238
  }
228
- else {
239
+ else
229
240
  _this.stopEmbeddedReadyAnnouncement();
230
- }
231
241
  }, this.options.connectionPingInterval);
232
242
  };
233
243
  WIO.prototype.stopEmbeddedReadyAnnouncement = function () {
234
- if (this.embeddedReadyCheckInterval) {
235
- clearInterval(this.embeddedReadyCheckInterval);
236
- this.embeddedReadyCheckInterval = undefined;
237
- }
244
+ if (!this.embeddedReadyCheckInterval)
245
+ return;
246
+ clearInterval(this.embeddedReadyCheckInterval);
247
+ this.embeddedReadyCheckInterval = undefined;
238
248
  };
239
249
  // Message rate limiting
240
250
  WIO.prototype.checkRateLimit = function () {
@@ -257,8 +267,8 @@ var WIO = /** @class */ (function () {
257
267
  };
258
268
  // Queue messages when not connected
259
269
  WIO.prototype.queueMessage = function (_event, payload, fn) {
270
+ // Remove oldest message
260
271
  if (this.messageQueue.length >= this.options.messageQueueSize) {
261
- // Remove oldest message
262
272
  var removed = this.messageQueue.shift();
263
273
  this.debug("[".concat(this.peer.type, "] Message queue full, removed oldest message:"), removed === null || removed === void 0 ? void 0 : removed._event);
264
274
  }
@@ -311,6 +321,9 @@ var WIO = /** @class */ (function () {
311
321
  };
312
322
  /**
313
323
  * Listening to connection from the WebView host
324
+ *
325
+ * NOTE: This is called manually from page code,
326
+ * not auto-initialized
314
327
  */
315
328
  WIO.prototype.listen = function (hostOrigin) {
316
329
  var _this = this;
@@ -320,9 +333,7 @@ var WIO = /** @class */ (function () {
320
333
  this.reconnectAttempts = 0;
321
334
  this.debug("[".concat(this.peer.type, "] Listening to connect").concat(hostOrigin ? ": Host <".concat(hostOrigin, ">") : ''));
322
335
  // Start announcing readiness
323
- setTimeout(function () {
324
- _this.announceEmbeddedReady();
325
- }, 100);
336
+ setTimeout(function () { return _this.announceEmbeddedReady(); }, 100);
326
337
  return this;
327
338
  };
328
339
  /**
@@ -359,9 +370,9 @@ var WIO = /** @class */ (function () {
359
370
  this.peer.embeddedReady = true;
360
371
  this.debug("[".concat(this.peer.type, "] Embedded peer ready"));
361
372
  // If we're WEBVIEW and not connected, send ping
362
- if (this.peer.type === 'WEBVIEW' && !this.peer.connected) {
363
- this.emit('ping', { token: this.connectionToken });
364
- }
373
+ this.peer.type === 'WEBVIEW'
374
+ && !this.peer.connected
375
+ && this.emit('ping', { token: this.connectionToken });
365
376
  return;
366
377
  }
367
378
  // Handle webview ready signal
@@ -424,6 +435,27 @@ var WIO = /** @class */ (function () {
424
435
  }
425
436
  return;
426
437
  }
438
+ // Optional application-level incoming validation (non-reserved events only)
439
+ if (!RESERVED_EVENTS.includes(_event)) {
440
+ if (this.options.allowedIncomingEvents
441
+ && !this.options.allowedIncomingEvents.includes(_event)) {
442
+ this.fire('error', {
443
+ type: 'DISALLOWED_EVENT',
444
+ direction: 'incoming',
445
+ event: _event
446
+ });
447
+ return;
448
+ }
449
+ if (this.options.validateIncoming
450
+ && !this.options.validateIncoming(_event, payload)) {
451
+ this.fire('error', {
452
+ type: 'INVALID_MESSAGE',
453
+ direction: 'incoming',
454
+ event: _event
455
+ });
456
+ return;
457
+ }
458
+ }
427
459
  // Fire available event listeners
428
460
  this.fire(_event, payload, cid);
429
461
  }
@@ -583,9 +615,7 @@ var WIO = /** @class */ (function () {
583
615
  var _this = this;
584
616
  if (timeout === void 0) { timeout = 5000; }
585
617
  return new Promise(function (resolve, reject) {
586
- var timeoutId = setTimeout(function () {
587
- reject(new Error("Event '".concat(_event, "' acknowledgment timeout after ").concat(timeout, "ms")));
588
- }, timeout);
618
+ var timeoutId = setTimeout(function () { return reject(new Error("Event '".concat(_event, "' acknowledgment timeout after ").concat(timeout, "ms"))); }, timeout);
589
619
  try {
590
620
  _this.emit(_event, payload, function (error) {
591
621
  var args = [];
@@ -677,9 +707,10 @@ var WIO = /** @class */ (function () {
677
707
  /**
678
708
  * Get injected JavaScript for WebView
679
709
  * Sets up the EMBEDDED side of the bridge
710
+ * NOTE: Does not auto-initialize - page must call window._wio.listen()
680
711
  */
681
712
  WIO.prototype.getInjectedJavaScript = function () {
682
- return "\n (function() {\n try {\n console.log('[EMBEDDED] Initializing WIO bridge...')\n \n const RESERVED_EVENTS = [\n 'ping',\n 'pong',\n '__heartbeat',\n '__heartbeat_response',\n '__embedded_ready',\n '__connection_ack',\n '__webview_ready'\n ]\n \n // Use closure variable to avoid 'this' binding issues\n window._wio = {\n type: 'EMBEDDED',\n connected: false,\n Events: {},\n messageQueue: [],\n connectionToken: null,\n setupComplete: false,\n\n listen: function(){\n console.log('[EMBEDDED] Setting up message listeners...')\n\n // Listen to messages from React Native\n window.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message ) // Use window._wio instead of this\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n \n // Android support\n if( typeof document !== 'undefined' ){\n document.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message ) // Use window._wio instead of this\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n }\n\n window._wio.setupComplete = true // Use window._wio instead of this\n console.log('[EMBEDDED] Setup complete')\n\n return window._wio\n },\n \n ackId: function(){\n const\n rmin = 100000,\n rmax = 999999,\n timestamp = Date.now(),\n random = Math.floor( Math.random() * ( rmax - rmin + 1 ) + rmin )\n\n return timestamp + '_' + random\n },\n \n fire: function( _event, payload, cid ){\n if( !window._wio.Events[_event] && !window._wio.Events[_event + '--@once'] ){\n console.log('[EMBEDDED] No listener for:', _event)\n return\n }\n \n const ackFn = cid\n ? ( error, ...args ) => window._wio.emit( _event + '--' + cid + '--@ack', { error: error || false, args } )\n : undefined\n \n let listeners = []\n if( window._wio.Events[_event + '--@once'] ){\n _event += '--@once'\n listeners = window._wio.Events[_event]\n\n delete window._wio.Events[_event]\n }\n else listeners = window._wio.Events[_event] || []\n \n listeners.forEach( fn => {\n try { payload !== undefined ? fn( payload, ackFn ) : fn( ackFn ) }\n catch( error ){ console.error('[EMBEDDED] Listener error:', error ) }\n })\n },\n \n emit: function( _event, payload, fn ){\n if( typeof payload === 'function' ){\n fn = payload\n payload = undefined\n }\n \n if( !window._wio.connected && !RESERVED_EVENTS.includes(_event) ){\n window._wio.messageQueue.push({ _event, payload, fn, timestamp: Date.now() })\n console.log('[EMBEDDED] Queued message:', _event)\n return\n }\n \n try {\n let cid\n if( typeof fn === 'function' ){\n cid = window._wio.ackId()\n window._wio.once( _event + '--' + cid + '--@ack', ({ error, args }) => fn( error, ...args ) )\n }\n \n const messageData = {\n _event,\n payload,\n cid,\n timestamp: Date.now(),\n token: RESERVED_EVENTS.includes(_event) ? window._wio.connectionToken : undefined\n }\n \n if( typeof window.ReactNativeWebView !== 'undefined' )\n window.ReactNativeWebView.postMessage( JSON.stringify( messageData ) )\n else console.error('[EMBEDDED] ReactNativeWebView not available')\n }\n catch( error ){\n console.error('[EMBEDDED] Emit error:', error )\n typeof fn === 'function' && fn( String(error) )\n }\n },\n \n on: function( _event, fn ){\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n once: function( _event, fn ){\n _event += '--@once'\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n off: function( _event, fn ){\n if( fn && window._wio.Events[_event] ){\n const index = window._wio.Events[_event].indexOf( fn )\n if( index > -1 ){\n window._wio.Events[_event].splice( index, 1 )\n if( window._wio.Events[_event].length === 0 ) delete window._wio.Events[_event]\n }\n }\n else delete window._wio.Events[_event]\n\n return window._wio\n },\n \n processMessageQueue: function(){\n if( !window._wio.connected || window._wio.messageQueue.length === 0 ) return\n \n console.log('[EMBEDDED] Processing', window._wio.messageQueue.length, 'queued messages')\n const queue = [ ...window._wio.messageQueue ]\n window._wio.messageQueue = []\n \n queue.forEach( msg => {\n try { window._wio.emit( msg._event, msg.payload, msg.fn ) }\n catch( error ){ console.error('[EMBEDDED] Queue process error:', error ) }\n })\n },\n \n handleMessage: function( data ){\n if( !data || !data._event ) return\n \n const { _event, payload, cid, token } = data\n \n console.log('[EMBEDDED] Received:', _event )\n \n // Handle heartbeat response\n if( _event === '__heartbeat_response' )\n return\n \n // Handle heartbeat request\n if( _event === '__heartbeat' ){\n window._wio.emit('__heartbeat_response', { timestamp: Date.now() })\n return\n }\n \n // Handle webview ready signal\n if( _event === '__webview_ready' ){\n console.log('[EMBEDDED] WebView ready signal received')\n return\n }\n \n // Handle ping from WEBVIEW\n if( _event === 'ping' ){\n console.log('[EMBEDDED] Received ping, sending pong')\n window._wio.connectionToken = token\n\n window._wio.emit('pong', { token: window._wio.connectionToken })\n return\n }\n \n // Handle connection acknowledgment\n if( _event === '__connection_ack' ){\n if( token && token !== window._wio.connectionToken ){\n console.error('[EMBEDDED] Invalid connection token in ack')\n return\n }\n \n console.log('[EMBEDDED] Connection established (received ack)')\n\n window._wio.connected = true\n window._wio.processMessageQueue()\n window._wio.fire('connect')\n\n return\n }\n \n // Fire event listeners\n window._wio.fire( _event, payload, cid )\n },\n \n announceReady: function(){\n let attempts = 0\n const maxAttempts = 5\n const interval = 2000\n \n console.log('[EMBEDDED] Starting ready announcements')\n \n const announce = () => {\n if( window._wio.connected ){\n console.log('[EMBEDDED] Connected, stopping announcements')\n return\n }\n \n attempts++\n if( attempts > maxAttempts ){\n console.log('[EMBEDDED] Max announcement attempts reached')\n return\n }\n \n console.log('[EMBEDDED] Announcing ready (attempt', attempts + '/' + maxAttempts + ')')\n window._wio.emit('__embedded_ready')\n \n setTimeout( announce, interval )\n }\n \n announce()\n }\n }\n\n // Initialize\n window._wio.listen()\n // Start announcing readiness after a short delay\n setTimeout(() => window._wio.announceReady(), 100)\n \n console.log('[EMBEDDED] WIO bridge initialized successfully')\n }\n catch( error ){\n console.error('[EMBEDDED] Setup failed:', error )\n \n // Create minimal fallback\n window._wio = {\n error: error.toString(),\n emit: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n on: function(){},\n once: function(){},\n off: function(){}\n }\n }\n\n true\n })()\n ";
713
+ return "\n (function() {\n try {\n console.debug('[EMBEDDED] Initializing WIO bridge...')\n \n const RESERVED_EVENTS = [\n 'ping',\n 'pong',\n '__heartbeat',\n '__heartbeat_response',\n '__embedded_ready',\n '__connection_ack',\n '__webview_ready'\n ]\n \n // Use closure variable to avoid 'this' binding issues\n window._wio = {\n type: 'EMBEDDED',\n connected: false,\n Events: {},\n messageQueue: [],\n connectionToken: null,\n setupComplete: false,\n\n listen: function(){\n if( this.setupComplete ){\n console.warn('[EMBEDDED] Already listening')\n return this\n }\n\n console.debug('[EMBEDDED] Setting up message listeners...')\n\n // Listen to messages from React Native\n window.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message )\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n \n // Android support\n if( typeof document !== 'undefined' ){\n document.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message )\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n }\n\n this.setupComplete = true\n console.debug('[EMBEDDED] Setup complete, starting ready announcements')\n\n // Start announcing readiness\n this.announceReady()\n\n return this\n },\n \n ackId: function(){\n const\n rmin = 100000,\n rmax = 999999,\n timestamp = Date.now(),\n random = Math.floor( Math.random() * ( rmax - rmin + 1 ) + rmin )\n\n return timestamp + '_' + random\n },\n \n fire: function( _event, payload, cid ){\n if( !window._wio.Events[_event] && !window._wio.Events[_event + '--@once'] ){\n console.debug('[EMBEDDED] No listener for:', _event)\n return\n }\n \n const ackFn = cid\n ? ( error, ...args ) => window._wio.emit( _event + '--' + cid + '--@ack', { error: error || false, args } )\n : undefined\n \n let listeners = []\n if( window._wio.Events[_event + '--@once'] ){\n _event += '--@once'\n listeners = window._wio.Events[_event]\n\n delete window._wio.Events[_event]\n }\n else listeners = window._wio.Events[_event] || []\n \n listeners.forEach( fn => {\n try { payload !== undefined ? fn( payload, ackFn ) : fn( ackFn ) }\n catch( error ){ console.error('[EMBEDDED] Listener error:', error ) }\n })\n },\n \n emit: function( _event, payload, fn ){\n if( typeof payload === 'function' ){\n fn = payload\n payload = undefined\n }\n \n if( !window._wio.connected && !RESERVED_EVENTS.includes(_event) ){\n window._wio.messageQueue.push({ _event, payload, fn, timestamp: Date.now() })\n console.debug('[EMBEDDED] Queued message:', _event)\n return\n }\n \n try {\n let cid\n if( typeof fn === 'function' ){\n cid = window._wio.ackId()\n window._wio.once( _event + '--' + cid + '--@ack', ({ error, args }) => fn( error, ...args ) )\n }\n \n const messageData = {\n _event,\n payload,\n cid,\n timestamp: Date.now(),\n token: RESERVED_EVENTS.includes(_event) ? window._wio.connectionToken : undefined\n }\n \n if( typeof window.ReactNativeWebView !== 'undefined' )\n window.ReactNativeWebView.postMessage( JSON.stringify( messageData ) )\n else console.error('[EMBEDDED] ReactNativeWebView not available')\n }\n catch( error ){\n console.error('[EMBEDDED] Emit error:', error )\n typeof fn === 'function' && fn( String(error) )\n }\n },\n \n on: function( _event, fn ){\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n once: function( _event, fn ){\n _event += '--@once'\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n off: function( _event, fn ){\n if( fn && window._wio.Events[_event] ){\n const index = window._wio.Events[_event].indexOf( fn )\n if( index > -1 ){\n window._wio.Events[_event].splice( index, 1 )\n if( window._wio.Events[_event].length === 0 ) delete window._wio.Events[_event]\n }\n }\n else delete window._wio.Events[_event]\n\n return window._wio\n },\n \n processMessageQueue: function(){\n if( !window._wio.connected || window._wio.messageQueue.length === 0 ) return\n \n console.debug('[EMBEDDED] Processing', window._wio.messageQueue.length, 'queued messages')\n const queue = [ ...window._wio.messageQueue ]\n window._wio.messageQueue = []\n \n queue.forEach( msg => {\n try { window._wio.emit( msg._event, msg.payload, msg.fn ) }\n catch( error ){ console.error('[EMBEDDED] Queue process error:', error ) }\n })\n },\n \n handleMessage: function( data ){\n if( !data || !data._event ) return\n \n const { _event, payload, cid, token } = data\n \n console.debug('[EMBEDDED] Received:', _event )\n \n // Handle heartbeat response\n if( _event === '__heartbeat_response' )\n return\n \n // Handle heartbeat request\n if( _event === '__heartbeat' ){\n window._wio.emit('__heartbeat_response', { timestamp: Date.now() })\n return\n }\n \n // Handle webview ready signal\n if( _event === '__webview_ready' ){\n console.debug('[EMBEDDED] WebView ready signal received')\n return\n }\n \n // Handle ping from WEBVIEW\n if( _event === 'ping' ){\n console.debug('[EMBEDDED] Received ping, sending pong')\n window._wio.connectionToken = token\n\n window._wio.emit('pong', { token: window._wio.connectionToken })\n return\n }\n \n // Handle connection acknowledgment\n if( _event === '__connection_ack' ){\n if( token && token !== window._wio.connectionToken ){\n console.error('[EMBEDDED] Invalid connection token in ack')\n return\n }\n \n console.debug('[EMBEDDED] Connection established (received ack)')\n\n window._wio.connected = true\n window._wio.processMessageQueue()\n window._wio.fire('connect')\n\n return\n }\n \n // Fire event listeners\n window._wio.fire( _event, payload, cid )\n },\n \n announceReady: function(){\n let attempts = 0\n const maxAttempts = 10\n const interval = 1000\n \n console.debug('[EMBEDDED] Starting ready announcements')\n \n const announce = () => {\n if( window._wio.connected ){\n console.debug('[EMBEDDED] Connected, stopping announcements')\n return\n }\n \n attempts++\n if( attempts > maxAttempts ){\n console.debug('[EMBEDDED] Max announcement attempts reached')\n return\n }\n \n console.debug('[EMBEDDED] Announcing ready (attempt', attempts + '/' + maxAttempts + ')')\n window._wio.emit('__embedded_ready')\n \n setTimeout( announce, interval )\n }\n \n announce()\n }\n }\n\n console.debug('[EMBEDDED] WIO bridge ready. Call window._wio.listen() to connect.')\n }\n catch( error ){\n console.error('[EMBEDDED] Setup failed:', error )\n \n // Create minimal fallback\n window._wio = {\n error: error.toString(),\n listen: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n emit: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n on: function(){},\n once: function(){},\n off: function(){}\n }\n }\n\n true\n })()\n ";
683
714
  };
684
715
  return WIO;
685
716
  }());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webview.io",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Easy and friendly API to connect and interact between React Native and WebView with enhanced security, reliability, and modern async/await support.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -17,6 +17,16 @@ export type Options = {
17
17
  messageQueueSize?: number
18
18
  connectionPingInterval?: number
19
19
  maxConnectionAttempts?: number
20
+ /**
21
+ * Optional allowlist of incoming application-level events.
22
+ * Reserved internal events (ping/pong/heartbeat/handshake) are always allowed.
23
+ */
24
+ allowedIncomingEvents?: string[]
25
+ /**
26
+ * Optional custom validator for incoming messages.
27
+ * Return false to drop a message; an 'error' event will be emitted.
28
+ */
29
+ validateIncoming?: ( event: string, payload: any ) => boolean
20
30
  }
21
31
 
22
32
  export interface RegisteredEvents {
@@ -83,7 +93,26 @@ const ackId = () => {
83
93
  }
84
94
 
85
95
  const generateToken = () => {
86
- return `${Date.now()}_${Math.random().toString(36).substring(2, 15)}`
96
+ // Prefer cryptographically strong randomness when available
97
+ try {
98
+ const globalCrypto = (typeof crypto !== 'undefined'
99
+ ? crypto
100
+ : (typeof window !== 'undefined' && (window as any).crypto)
101
+ || (typeof globalThis !== 'undefined' && (globalThis as any).crypto))
102
+
103
+ if( globalCrypto && typeof globalCrypto.getRandomValues === 'function' ){
104
+ const buffer = new Uint32Array(4)
105
+ globalCrypto.getRandomValues( buffer )
106
+
107
+ const randomPart = Array.from( buffer ).map( n => n.toString( 16 ) ).join('')
108
+ return `${Date.now()}_${randomPart}`
109
+ }
110
+ }
111
+ catch{
112
+ // Fall back to Math.random-based implementation below
113
+ }
114
+
115
+ return `${Date.now()}_${Math.random().toString( 36 ).substring( 2, 15 )}`
87
116
  }
88
117
 
89
118
  const RESERVED_EVENTS = [
@@ -131,8 +160,7 @@ export default class WIO {
131
160
  this.Events = {}
132
161
  this.peer = { type: 'WEBVIEW', connected: false, embeddedReady: false }
133
162
 
134
- if( options.type )
135
- this.peer.type = options.type
163
+ if( options.type ) this.peer.type = options.type
136
164
  }
137
165
 
138
166
  debug( ...args: any[] ){
@@ -211,24 +239,19 @@ export default class WIO {
211
239
  this.connectionToken = generateToken()
212
240
 
213
241
  // Re-initiate connection for WEBVIEW type
214
- if( this.peer.type === 'WEBVIEW' ){
215
- this.startConnectionAttempt()
216
- }
217
-
242
+ this.peer.type === 'WEBVIEW' && this.startConnectionAttempt()
218
243
  // For EMBEDDED type, announce readiness
219
- if( this.peer.type === 'EMBEDDED' ){
220
- this.announceEmbeddedReady()
221
- }
244
+ this.peer.type === 'EMBEDDED' && this.announceEmbeddedReady()
222
245
 
223
246
  // Set timeout for this reconnection attempt
224
- setTimeout(() => {
247
+ setTimeout( () => {
225
248
  if( this.peer.connected ) return
226
249
 
227
250
  this.reconnectAttempts < this.maxReconnectAttempts
228
251
  ? this.attemptReconnection()
229
252
  : this.fire('reconnection_failed', { attempts: this.reconnectAttempts })
230
- }, this.options.connectionTimeout!)
231
- }, delay)
253
+ }, this.options.connectionTimeout! )
254
+ }, delay )
232
255
  }
233
256
 
234
257
  // Start connection attempt with timeout and retries
@@ -257,10 +280,8 @@ export default class WIO {
257
280
  this.debug(`[${this.peer.type}] Connection attempt ${this.connectionAttempts}/${this.options.maxConnectionAttempts}`)
258
281
  this.emit('ping', { token: this.connectionToken })
259
282
  }
260
- else {
261
- this.stopConnectionAttempt()
262
- }
263
- }, this.options.connectionPingInterval!)
283
+ else this.stopConnectionAttempt()
284
+ }, this.options.connectionPingInterval! )
264
285
 
265
286
  // Set overall timeout
266
287
  this.connectionAttemptTimer = setTimeout(() => {
@@ -310,17 +331,15 @@ export default class WIO {
310
331
  this.debug(`[${this.peer.type}] Ready announcement attempt ${attempts}/${maxAttempts}`)
311
332
  this.emit('__embedded_ready')
312
333
  }
313
- else {
314
- this.stopEmbeddedReadyAnnouncement()
315
- }
334
+ else this.stopEmbeddedReadyAnnouncement()
316
335
  }, this.options.connectionPingInterval!)
317
336
  }
318
337
 
319
338
  private stopEmbeddedReadyAnnouncement(){
320
- if( this.embeddedReadyCheckInterval ){
321
- clearInterval( this.embeddedReadyCheckInterval )
322
- this.embeddedReadyCheckInterval = undefined
323
- }
339
+ if( !this.embeddedReadyCheckInterval ) return
340
+
341
+ clearInterval( this.embeddedReadyCheckInterval )
342
+ this.embeddedReadyCheckInterval = undefined
324
343
  }
325
344
 
326
345
  // Message rate limiting
@@ -351,8 +370,8 @@ export default class WIO {
351
370
 
352
371
  // Queue messages when not connected
353
372
  private queueMessage( _event: string, payload?: any, fn?: AckFunction ){
373
+ // Remove oldest message
354
374
  if( this.messageQueue.length >= this.options.messageQueueSize! ){
355
- // Remove oldest message
356
375
  const removed = this.messageQueue.shift()
357
376
  this.debug(`[${this.peer.type}] Message queue full, removed oldest message:`, removed?._event)
358
377
  }
@@ -413,6 +432,9 @@ export default class WIO {
413
432
 
414
433
  /**
415
434
  * Listening to connection from the WebView host
435
+ *
436
+ * NOTE: This is called manually from page code,
437
+ * not auto-initialized
416
438
  */
417
439
  listen( hostOrigin?: string ){
418
440
  this.peer.type = 'EMBEDDED'
@@ -423,9 +445,7 @@ export default class WIO {
423
445
  this.debug(`[${this.peer.type}] Listening to connect${hostOrigin ? `: Host <${hostOrigin}>` : ''}`)
424
446
 
425
447
  // Start announcing readiness
426
- setTimeout(() => {
427
- this.announceEmbeddedReady()
428
- }, 100)
448
+ setTimeout( () => this.announceEmbeddedReady(), 100 )
429
449
 
430
450
  return this
431
451
  }
@@ -436,9 +456,9 @@ export default class WIO {
436
456
  handleMessage( event: { nativeEvent: { data: string } } ){
437
457
  try {
438
458
  const data = JSON.parse( event.nativeEvent.data )
439
-
440
459
  // Enhanced security: check valid message structure
441
- if( typeof data !== 'object' || !data.hasOwnProperty('_event') ) return
460
+ if( typeof data !== 'object' || !data.hasOwnProperty('_event') )
461
+ return
442
462
 
443
463
  const { _event, payload, cid, timestamp, token } = data as MessageData
444
464
 
@@ -470,9 +490,10 @@ export default class WIO {
470
490
  this.debug(`[${this.peer.type}] Embedded peer ready`)
471
491
 
472
492
  // If we're WEBVIEW and not connected, send ping
473
- if( this.peer.type === 'WEBVIEW' && !this.peer.connected ){
474
- this.emit('ping', { token: this.connectionToken })
475
- }
493
+ this.peer.type === 'WEBVIEW'
494
+ && !this.peer.connected
495
+ && this.emit('ping', { token: this.connectionToken })
496
+
476
497
  return
477
498
  }
478
499
 
@@ -494,6 +515,7 @@ export default class WIO {
494
515
  // Don't set fully connected yet - wait for ack
495
516
  this.debug(`[${this.peer.type}] Received ping, sent pong`)
496
517
  }
518
+
497
519
  return
498
520
  }
499
521
 
@@ -522,6 +544,7 @@ export default class WIO {
522
544
 
523
545
  this.debug(`[${this.peer.type}] Connected (3-way handshake complete)`)
524
546
  }
547
+
525
548
  return
526
549
  }
527
550
 
@@ -546,9 +569,33 @@ export default class WIO {
546
569
 
547
570
  this.debug(`[${this.peer.type}] Connected (received ack)`)
548
571
  }
572
+
549
573
  return
550
574
  }
551
575
 
576
+ // Optional application-level incoming validation (non-reserved events only)
577
+ if( !RESERVED_EVENTS.includes( _event ) ){
578
+ if( this.options.allowedIncomingEvents
579
+ && !this.options.allowedIncomingEvents.includes( _event ) ){
580
+ this.fire('error', {
581
+ type: 'DISALLOWED_EVENT',
582
+ direction: 'incoming',
583
+ event: _event
584
+ })
585
+ return
586
+ }
587
+
588
+ if( this.options.validateIncoming
589
+ && !this.options.validateIncoming( _event, payload ) ){
590
+ this.fire('error', {
591
+ type: 'INVALID_MESSAGE',
592
+ direction: 'incoming',
593
+ event: _event
594
+ })
595
+ return
596
+ }
597
+ }
598
+
552
599
  // Fire available event listeners
553
600
  this.fire( _event, payload, cid )
554
601
  }
@@ -569,11 +616,11 @@ export default class WIO {
569
616
  }
570
617
 
571
618
  const ackFn = cid
572
- ? ( error: boolean | string, ...args: any[] ): void => {
573
- this.emit(`${_event}--${cid}--@ack`, { error: error || false, args })
574
- return
575
- }
576
- : undefined
619
+ ? ( error: boolean | string, ...args: any[] ): void => {
620
+ this.emit(`${_event}--${cid}--@ack`, { error: error || false, args })
621
+ return
622
+ }
623
+ : undefined
577
624
  let listeners: Listener[] = []
578
625
 
579
626
  if( this.Events[_event + '--@once'] ){
@@ -717,9 +764,7 @@ export default class WIO {
717
764
 
718
765
  emitAsync<T = any, R = any>( _event: string, payload?: T, timeout: number = 5000 ): Promise<R> {
719
766
  return new Promise(( resolve, reject ) => {
720
- const timeoutId = setTimeout(() => {
721
- reject( new Error(`Event '${_event}' acknowledgment timeout after ${timeout}ms`) )
722
- }, timeout )
767
+ const timeoutId = setTimeout(() => reject( new Error(`Event '${_event}' acknowledgment timeout after ${timeout}ms`) ), timeout )
723
768
 
724
769
  try {
725
770
  this.emit( _event, payload, ( error, ...args ) => {
@@ -745,17 +790,17 @@ export default class WIO {
745
790
  return new Promise(( resolve, reject ) => {
746
791
  if( this.isConnected() ) return resolve()
747
792
 
748
- const timeoutId = setTimeout(() => {
749
- this.off('connect', connectHandler)
793
+ const timeoutId = setTimeout( () => {
794
+ this.off('connect', connectHandler )
750
795
  reject( new Error('Connection timeout') )
751
- }, timeout || this.options.connectionTimeout)
796
+ }, timeout || this.options.connectionTimeout )
752
797
 
753
798
  const connectHandler = () => {
754
799
  clearTimeout( timeoutId )
755
800
  resolve()
756
801
  }
757
802
 
758
- this.once('connect', connectHandler)
803
+ this.once('connect', connectHandler )
759
804
  })
760
805
  }
761
806
 
@@ -822,12 +867,13 @@ export default class WIO {
822
867
  /**
823
868
  * Get injected JavaScript for WebView
824
869
  * Sets up the EMBEDDED side of the bridge
870
+ * NOTE: Does not auto-initialize - page must call window._wio.listen()
825
871
  */
826
872
  getInjectedJavaScript(): string {
827
873
  return `
828
874
  (function() {
829
875
  try {
830
- console.log('[EMBEDDED] Initializing WIO bridge...')
876
+ console.debug('[EMBEDDED] Initializing WIO bridge...')
831
877
 
832
878
  const RESERVED_EVENTS = [
833
879
  'ping',
@@ -849,13 +895,18 @@ export default class WIO {
849
895
  setupComplete: false,
850
896
 
851
897
  listen: function(){
852
- console.log('[EMBEDDED] Setting up message listeners...')
898
+ if( this.setupComplete ){
899
+ console.warn('[EMBEDDED] Already listening')
900
+ return this
901
+ }
902
+
903
+ console.debug('[EMBEDDED] Setting up message listeners...')
853
904
 
854
905
  // Listen to messages from React Native
855
906
  window.addEventListener('message', function( event ){
856
907
  try {
857
908
  const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data
858
- window._wio.handleMessage( message ) // Use window._wio instead of this
909
+ window._wio.handleMessage( message )
859
910
  }
860
911
  catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }
861
912
  })
@@ -865,16 +916,19 @@ export default class WIO {
865
916
  document.addEventListener('message', function( event ){
866
917
  try {
867
918
  const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data
868
- window._wio.handleMessage( message ) // Use window._wio instead of this
919
+ window._wio.handleMessage( message )
869
920
  }
870
921
  catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }
871
922
  })
872
923
  }
873
924
 
874
- window._wio.setupComplete = true // Use window._wio instead of this
875
- console.log('[EMBEDDED] Setup complete')
925
+ this.setupComplete = true
926
+ console.debug('[EMBEDDED] Setup complete, starting ready announcements')
876
927
 
877
- return window._wio
928
+ // Start announcing readiness
929
+ this.announceReady()
930
+
931
+ return this
878
932
  },
879
933
 
880
934
  ackId: function(){
@@ -889,7 +943,7 @@ export default class WIO {
889
943
 
890
944
  fire: function( _event, payload, cid ){
891
945
  if( !window._wio.Events[_event] && !window._wio.Events[_event + '--@once'] ){
892
- console.log('[EMBEDDED] No listener for:', _event)
946
+ console.debug('[EMBEDDED] No listener for:', _event)
893
947
  return
894
948
  }
895
949
 
@@ -920,7 +974,7 @@ export default class WIO {
920
974
 
921
975
  if( !window._wio.connected && !RESERVED_EVENTS.includes(_event) ){
922
976
  window._wio.messageQueue.push({ _event, payload, fn, timestamp: Date.now() })
923
- console.log('[EMBEDDED] Queued message:', _event)
977
+ console.debug('[EMBEDDED] Queued message:', _event)
924
978
  return
925
979
  }
926
980
 
@@ -981,7 +1035,7 @@ export default class WIO {
981
1035
  processMessageQueue: function(){
982
1036
  if( !window._wio.connected || window._wio.messageQueue.length === 0 ) return
983
1037
 
984
- console.log('[EMBEDDED] Processing', window._wio.messageQueue.length, 'queued messages')
1038
+ console.debug('[EMBEDDED] Processing', window._wio.messageQueue.length, 'queued messages')
985
1039
  const queue = [ ...window._wio.messageQueue ]
986
1040
  window._wio.messageQueue = []
987
1041
 
@@ -996,7 +1050,7 @@ export default class WIO {
996
1050
 
997
1051
  const { _event, payload, cid, token } = data
998
1052
 
999
- console.log('[EMBEDDED] Received:', _event )
1053
+ console.debug('[EMBEDDED] Received:', _event )
1000
1054
 
1001
1055
  // Handle heartbeat response
1002
1056
  if( _event === '__heartbeat_response' )
@@ -1010,13 +1064,13 @@ export default class WIO {
1010
1064
 
1011
1065
  // Handle webview ready signal
1012
1066
  if( _event === '__webview_ready' ){
1013
- console.log('[EMBEDDED] WebView ready signal received')
1067
+ console.debug('[EMBEDDED] WebView ready signal received')
1014
1068
  return
1015
1069
  }
1016
1070
 
1017
1071
  // Handle ping from WEBVIEW
1018
1072
  if( _event === 'ping' ){
1019
- console.log('[EMBEDDED] Received ping, sending pong')
1073
+ console.debug('[EMBEDDED] Received ping, sending pong')
1020
1074
  window._wio.connectionToken = token
1021
1075
 
1022
1076
  window._wio.emit('pong', { token: window._wio.connectionToken })
@@ -1030,7 +1084,7 @@ export default class WIO {
1030
1084
  return
1031
1085
  }
1032
1086
 
1033
- console.log('[EMBEDDED] Connection established (received ack)')
1087
+ console.debug('[EMBEDDED] Connection established (received ack)')
1034
1088
 
1035
1089
  window._wio.connected = true
1036
1090
  window._wio.processMessageQueue()
@@ -1045,24 +1099,24 @@ export default class WIO {
1045
1099
 
1046
1100
  announceReady: function(){
1047
1101
  let attempts = 0
1048
- const maxAttempts = 5
1049
- const interval = 2000
1102
+ const maxAttempts = 10
1103
+ const interval = 1000
1050
1104
 
1051
- console.log('[EMBEDDED] Starting ready announcements')
1105
+ console.debug('[EMBEDDED] Starting ready announcements')
1052
1106
 
1053
1107
  const announce = () => {
1054
1108
  if( window._wio.connected ){
1055
- console.log('[EMBEDDED] Connected, stopping announcements')
1109
+ console.debug('[EMBEDDED] Connected, stopping announcements')
1056
1110
  return
1057
1111
  }
1058
1112
 
1059
1113
  attempts++
1060
1114
  if( attempts > maxAttempts ){
1061
- console.log('[EMBEDDED] Max announcement attempts reached')
1115
+ console.debug('[EMBEDDED] Max announcement attempts reached')
1062
1116
  return
1063
1117
  }
1064
1118
 
1065
- console.log('[EMBEDDED] Announcing ready (attempt', attempts + '/' + maxAttempts + ')')
1119
+ console.debug('[EMBEDDED] Announcing ready (attempt', attempts + '/' + maxAttempts + ')')
1066
1120
  window._wio.emit('__embedded_ready')
1067
1121
 
1068
1122
  setTimeout( announce, interval )
@@ -1072,12 +1126,7 @@ export default class WIO {
1072
1126
  }
1073
1127
  }
1074
1128
 
1075
- // Initialize
1076
- window._wio.listen()
1077
- // Start announcing readiness after a short delay
1078
- setTimeout(() => window._wio.announceReady(), 100)
1079
-
1080
- console.log('[EMBEDDED] WIO bridge initialized successfully')
1129
+ console.debug('[EMBEDDED] WIO bridge ready. Call window._wio.listen() to connect.')
1081
1130
  }
1082
1131
  catch( error ){
1083
1132
  console.error('[EMBEDDED] Setup failed:', error )
@@ -1085,6 +1134,7 @@ export default class WIO {
1085
1134
  // Create minimal fallback
1086
1135
  window._wio = {
1087
1136
  error: error.toString(),
1137
+ listen: function(){ console.error('[EMBEDDED] WIO failed to initialize') },
1088
1138
  emit: function(){ console.error('[EMBEDDED] WIO failed to initialize') },
1089
1139
  on: function(){},
1090
1140
  once: function(){},