webview.io 1.0.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/LICENSE +21 -0
- package/README.md +496 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +511 -0
- package/package.json +69 -0
- package/src/index.ts +768 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
14
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
15
|
+
if (ar || !(i in from)) {
|
|
16
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
17
|
+
ar[i] = from[i];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
function newObject(data) {
|
|
24
|
+
return JSON.parse(JSON.stringify(data));
|
|
25
|
+
}
|
|
26
|
+
function getMessageSize(data) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.stringify(data).length;
|
|
29
|
+
}
|
|
30
|
+
catch (_a) {
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function sanitizePayload(payload, maxSize) {
|
|
35
|
+
if (!payload)
|
|
36
|
+
return payload;
|
|
37
|
+
var size = getMessageSize(payload);
|
|
38
|
+
if (size > maxSize)
|
|
39
|
+
throw new Error("Message size ".concat(size, " exceeds limit ").concat(maxSize));
|
|
40
|
+
// Basic sanitization - remove functions and undefined values
|
|
41
|
+
return JSON.parse(JSON.stringify(payload));
|
|
42
|
+
}
|
|
43
|
+
var ackId = function () {
|
|
44
|
+
var rmin = 100000, rmax = 999999, timestamp = Date.now(), random = Math.floor(Math.random() * (rmax - rmin + 1) + rmin);
|
|
45
|
+
return "".concat(timestamp, "_").concat(random);
|
|
46
|
+
};
|
|
47
|
+
var RESERVED_EVENTS = [
|
|
48
|
+
'ping',
|
|
49
|
+
'pong',
|
|
50
|
+
'__heartbeat',
|
|
51
|
+
'__heartbeat_response'
|
|
52
|
+
];
|
|
53
|
+
var WIO = /** @class */ (function () {
|
|
54
|
+
function WIO(options) {
|
|
55
|
+
if (options === void 0) { options = {}; }
|
|
56
|
+
this.messageQueue = [];
|
|
57
|
+
this.messageRateTracker = [];
|
|
58
|
+
this.reconnectAttempts = 0;
|
|
59
|
+
this.maxReconnectAttempts = 5;
|
|
60
|
+
if (options && typeof options !== 'object')
|
|
61
|
+
throw new Error('Invalid Options');
|
|
62
|
+
this.options = __assign({ debug: false, heartbeatInterval: 30000, connectionTimeout: 10000, maxMessageSize: 1024 * 1024, maxMessagesPerSecond: 100, autoReconnect: true, messageQueueSize: 50 }, options);
|
|
63
|
+
this.Events = {};
|
|
64
|
+
this.peer = { type: 'WEBVIEW', connected: false };
|
|
65
|
+
if (options.type)
|
|
66
|
+
this.peer.type = options.type;
|
|
67
|
+
}
|
|
68
|
+
WIO.prototype.debug = function () {
|
|
69
|
+
var args = [];
|
|
70
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
71
|
+
args[_i] = arguments[_i];
|
|
72
|
+
}
|
|
73
|
+
this.options.debug && console.debug.apply(console, args);
|
|
74
|
+
};
|
|
75
|
+
WIO.prototype.isConnected = function () {
|
|
76
|
+
return !!this.peer.connected && !!this.peer.webViewRef;
|
|
77
|
+
};
|
|
78
|
+
// Enhanced connection health monitoring
|
|
79
|
+
WIO.prototype.startHeartbeat = function () {
|
|
80
|
+
var _this = this;
|
|
81
|
+
if (!this.options.heartbeatInterval)
|
|
82
|
+
return;
|
|
83
|
+
this.heartbeatTimer = setInterval(function () {
|
|
84
|
+
if (_this.isConnected()) {
|
|
85
|
+
var now = Date.now();
|
|
86
|
+
// Check if peer is still responsive
|
|
87
|
+
if (_this.peer.lastHeartbeat
|
|
88
|
+
&& (now - _this.peer.lastHeartbeat) > (_this.options.heartbeatInterval * 2)) {
|
|
89
|
+
_this.debug("[".concat(_this.peer.type, "] Heartbeat timeout detected"));
|
|
90
|
+
_this.handleConnectionLoss();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Send heartbeat
|
|
94
|
+
try {
|
|
95
|
+
_this.emit('__heartbeat', { timestamp: now });
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
_this.debug("[".concat(_this.peer.type, "] Heartbeat send failed:"), error);
|
|
99
|
+
_this.handleConnectionLoss();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}, this.options.heartbeatInterval);
|
|
103
|
+
};
|
|
104
|
+
WIO.prototype.stopHeartbeat = function () {
|
|
105
|
+
if (!this.heartbeatTimer)
|
|
106
|
+
return;
|
|
107
|
+
clearInterval(this.heartbeatTimer);
|
|
108
|
+
this.heartbeatTimer = undefined;
|
|
109
|
+
};
|
|
110
|
+
// Handle connection loss and potential reconnection
|
|
111
|
+
WIO.prototype.handleConnectionLoss = function () {
|
|
112
|
+
if (!this.peer.connected)
|
|
113
|
+
return;
|
|
114
|
+
this.peer.connected = false;
|
|
115
|
+
this.stopHeartbeat();
|
|
116
|
+
this.fire('disconnect', { reason: 'CONNECTION_LOST' });
|
|
117
|
+
this.options.autoReconnect
|
|
118
|
+
&& this.reconnectAttempts < this.maxReconnectAttempts
|
|
119
|
+
&& this.attemptReconnection();
|
|
120
|
+
};
|
|
121
|
+
WIO.prototype.attemptReconnection = function () {
|
|
122
|
+
var _this = this;
|
|
123
|
+
if (this.reconnectTimer)
|
|
124
|
+
return;
|
|
125
|
+
this.reconnectAttempts++;
|
|
126
|
+
var delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 30000); // Exponential backoff, max 30s
|
|
127
|
+
this.debug("[".concat(this.peer.type, "] Attempting reconnection ").concat(this.reconnectAttempts, "/").concat(this.maxReconnectAttempts, " in ").concat(delay, "ms"));
|
|
128
|
+
this.fire('reconnecting', { attempt: this.reconnectAttempts, delay: delay });
|
|
129
|
+
this.reconnectTimer = setTimeout(function () {
|
|
130
|
+
_this.reconnectTimer = undefined;
|
|
131
|
+
// Re-initiate connection for WEBVIEW type
|
|
132
|
+
_this.peer.type === 'WEBVIEW'
|
|
133
|
+
&& _this.emit('ping');
|
|
134
|
+
// For EMBEDDED type, just wait for incoming connection
|
|
135
|
+
// Set timeout for this reconnection attempt
|
|
136
|
+
setTimeout(function () {
|
|
137
|
+
if (_this.peer.connected)
|
|
138
|
+
return;
|
|
139
|
+
_this.reconnectAttempts < _this.maxReconnectAttempts
|
|
140
|
+
? _this.attemptReconnection()
|
|
141
|
+
: _this.fire('reconnection_failed', { attempts: _this.reconnectAttempts });
|
|
142
|
+
}, _this.options.connectionTimeout);
|
|
143
|
+
}, delay);
|
|
144
|
+
};
|
|
145
|
+
// Message rate limiting
|
|
146
|
+
WIO.prototype.checkRateLimit = function () {
|
|
147
|
+
if (!this.options.maxMessagesPerSecond)
|
|
148
|
+
return true;
|
|
149
|
+
var now = Date.now(), aSecondAgo = now - 1000;
|
|
150
|
+
// Clean old entries
|
|
151
|
+
this.messageRateTracker = this.messageRateTracker.filter(function (timestamp) { return timestamp > aSecondAgo; });
|
|
152
|
+
// Check if limit exceeded
|
|
153
|
+
if (this.messageRateTracker.length >= this.options.maxMessagesPerSecond) {
|
|
154
|
+
this.fire('error', {
|
|
155
|
+
type: 'RATE_LIMIT_EXCEEDED',
|
|
156
|
+
limit: this.options.maxMessagesPerSecond,
|
|
157
|
+
current: this.messageRateTracker.length
|
|
158
|
+
});
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
this.messageRateTracker.push(now);
|
|
162
|
+
return true;
|
|
163
|
+
};
|
|
164
|
+
// Queue messages when not connected
|
|
165
|
+
WIO.prototype.queueMessage = function (_event, payload, fn) {
|
|
166
|
+
if (this.messageQueue.length >= this.options.messageQueueSize) {
|
|
167
|
+
// Remove oldest message
|
|
168
|
+
var removed = this.messageQueue.shift();
|
|
169
|
+
this.debug("[".concat(this.peer.type, "] Message queue full, removed oldest message:"), removed === null || removed === void 0 ? void 0 : removed._event);
|
|
170
|
+
}
|
|
171
|
+
this.messageQueue.push({
|
|
172
|
+
_event: _event,
|
|
173
|
+
payload: payload,
|
|
174
|
+
fn: fn,
|
|
175
|
+
timestamp: Date.now()
|
|
176
|
+
});
|
|
177
|
+
this.debug("[".concat(this.peer.type, "] Queued message: ").concat(_event, " (queue size: ").concat(this.messageQueue.length, ")"));
|
|
178
|
+
};
|
|
179
|
+
// Process queued messages when connection is established
|
|
180
|
+
WIO.prototype.processMessageQueue = function () {
|
|
181
|
+
var _this = this;
|
|
182
|
+
if (!this.isConnected() || this.messageQueue.length === 0)
|
|
183
|
+
return;
|
|
184
|
+
this.debug("[".concat(this.peer.type, "] Processing ").concat(this.messageQueue.length, " queued messages"));
|
|
185
|
+
var queue = __spreadArray([], this.messageQueue, true);
|
|
186
|
+
this.messageQueue = [];
|
|
187
|
+
queue.forEach(function (message) {
|
|
188
|
+
try {
|
|
189
|
+
_this.emit(message._event, message.payload, message.fn);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
_this.debug("[".concat(_this.peer.type, "] Failed to send queued message:"), error);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Establish a connection with WebView
|
|
198
|
+
*/
|
|
199
|
+
WIO.prototype.initiate = function (webViewRef, origin) {
|
|
200
|
+
if (!webViewRef || !origin)
|
|
201
|
+
throw new Error('Invalid Connection initiation arguments');
|
|
202
|
+
if (this.peer.type === 'EMBEDDED')
|
|
203
|
+
throw new Error('Expect EMBEDDED to <listen> and WEBVIEW to <initiate> a connection');
|
|
204
|
+
// Clean up existing resources if any
|
|
205
|
+
this.cleanup();
|
|
206
|
+
this.peer.webViewRef = webViewRef;
|
|
207
|
+
this.peer.origin = origin;
|
|
208
|
+
this.peer.connected = false;
|
|
209
|
+
this.reconnectAttempts = 0;
|
|
210
|
+
this.debug("[".concat(this.peer.type, "] Initiate connection: WebView origin <").concat(origin, ">"));
|
|
211
|
+
this.emit('ping');
|
|
212
|
+
return this;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Listening to connection from the WebView host
|
|
216
|
+
* Note: In React Native context, this is handled by injected JavaScript
|
|
217
|
+
*/
|
|
218
|
+
WIO.prototype.listen = function (hostOrigin) {
|
|
219
|
+
this.peer.type = 'EMBEDDED'; // iframe.io-rn connection listener is automatically set as EMBEDDED
|
|
220
|
+
this.peer.connected = false;
|
|
221
|
+
this.reconnectAttempts = 0;
|
|
222
|
+
this.debug("[".concat(this.peer.type, "] Listening to connect").concat(hostOrigin ? ": Host <".concat(hostOrigin, ">") : ''));
|
|
223
|
+
return this;
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Handle incoming message from WebView
|
|
227
|
+
* Called by React Native component via onMessage prop
|
|
228
|
+
*/
|
|
229
|
+
WIO.prototype.handleMessage = function (event) {
|
|
230
|
+
try {
|
|
231
|
+
var data = JSON.parse(event.nativeEvent.data);
|
|
232
|
+
// Enhanced security: check valid message structure
|
|
233
|
+
if (typeof data !== 'object' || !data.hasOwnProperty('_event'))
|
|
234
|
+
return;
|
|
235
|
+
var _a = data, _event = _a._event, payload = _a.payload, cid = _a.cid, timestamp = _a.timestamp;
|
|
236
|
+
// Handle heartbeat responses
|
|
237
|
+
if (_event === '__heartbeat_response') {
|
|
238
|
+
this.peer.lastHeartbeat = Date.now();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Handle heartbeat requests
|
|
242
|
+
if (_event === '__heartbeat') {
|
|
243
|
+
this.emit('__heartbeat_response', { timestamp: Date.now() });
|
|
244
|
+
this.peer.lastHeartbeat = Date.now();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.debug("[".concat(this.peer.type, "] Message: ").concat(_event), payload || '');
|
|
248
|
+
// Handshake or availability check events
|
|
249
|
+
if (_event == 'pong') {
|
|
250
|
+
// WebView is connected
|
|
251
|
+
this.peer.connected = true;
|
|
252
|
+
this.reconnectAttempts = 0;
|
|
253
|
+
this.peer.lastHeartbeat = Date.now();
|
|
254
|
+
this.startHeartbeat();
|
|
255
|
+
this.fire('connect');
|
|
256
|
+
this.processMessageQueue();
|
|
257
|
+
this.debug("[".concat(this.peer.type, "] connected"));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Fire available event listeners
|
|
261
|
+
this.fire(_event, payload, cid);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
this.debug("[".concat(this.peer.type, "] Message handling error:"), error);
|
|
265
|
+
this.fire('error', {
|
|
266
|
+
type: 'MESSAGE_HANDLING_ERROR',
|
|
267
|
+
error: error instanceof Error ? error.message : String(error)
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
WIO.prototype.fire = function (_event, payload, cid) {
|
|
272
|
+
var _this = this;
|
|
273
|
+
// Volatile event - check if any listeners exist
|
|
274
|
+
if (!this.Events[_event] && !this.Events[_event + '--@once']) {
|
|
275
|
+
this.debug("[".concat(this.peer.type, "] No <").concat(_event, "> listener defined"));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
var ackFn = cid
|
|
279
|
+
? function (error) {
|
|
280
|
+
var args = [];
|
|
281
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
282
|
+
args[_i - 1] = arguments[_i];
|
|
283
|
+
}
|
|
284
|
+
_this.emit("".concat(_event, "--").concat(cid, "--@ack"), { error: error || false, args: args });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
: undefined;
|
|
288
|
+
var listeners = [];
|
|
289
|
+
if (this.Events[_event + '--@once']) {
|
|
290
|
+
// Once triggable event
|
|
291
|
+
_event += '--@once';
|
|
292
|
+
listeners = this.Events[_event];
|
|
293
|
+
// Delete once event listeners after fired
|
|
294
|
+
delete this.Events[_event];
|
|
295
|
+
}
|
|
296
|
+
else
|
|
297
|
+
listeners = this.Events[_event];
|
|
298
|
+
// Fire listeners with error handling
|
|
299
|
+
listeners.forEach(function (fn) {
|
|
300
|
+
try {
|
|
301
|
+
payload !== undefined ? fn(payload, ackFn) : fn(ackFn);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
_this.debug("[".concat(_this.peer.type, "] Listener error for ").concat(_event, ":"), error);
|
|
305
|
+
_this.fire('error', {
|
|
306
|
+
type: 'LISTENER_ERROR',
|
|
307
|
+
event: _event,
|
|
308
|
+
error: error instanceof Error ? error.message : String(error)
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
WIO.prototype.emit = function (_event, payload, fn) {
|
|
314
|
+
var _a;
|
|
315
|
+
// Check rate limiting
|
|
316
|
+
if (!this.checkRateLimit())
|
|
317
|
+
return this;
|
|
318
|
+
/**
|
|
319
|
+
* Queue message if not connected: Except for
|
|
320
|
+
* connection-related events
|
|
321
|
+
*/
|
|
322
|
+
if (!this.isConnected() && !RESERVED_EVENTS.includes(_event)) {
|
|
323
|
+
this.queueMessage(_event, payload, fn);
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
if (!this.peer.webViewRef) {
|
|
327
|
+
this.fire('error', { type: 'NO_CONNECTION', event: _event });
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
if (typeof payload == 'function') {
|
|
331
|
+
fn = payload;
|
|
332
|
+
payload = undefined;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
// Enhanced security: sanitize and validate payload
|
|
336
|
+
var sanitizedPayload = payload
|
|
337
|
+
? sanitizePayload(payload, this.options.maxMessageSize)
|
|
338
|
+
: payload;
|
|
339
|
+
// Acknowledge event listener
|
|
340
|
+
var cid = void 0;
|
|
341
|
+
if (typeof fn === 'function') {
|
|
342
|
+
var ackFunction_1 = fn;
|
|
343
|
+
cid = ackId();
|
|
344
|
+
this.once("".concat(_event, "--").concat(cid, "--@ack"), function (_a) {
|
|
345
|
+
var error = _a.error, args = _a.args;
|
|
346
|
+
return ackFunction_1.apply(void 0, __spreadArray([error], args, false));
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
var messageData = {
|
|
350
|
+
_event: _event,
|
|
351
|
+
payload: sanitizedPayload,
|
|
352
|
+
cid: cid,
|
|
353
|
+
timestamp: Date.now(),
|
|
354
|
+
size: getMessageSize(sanitizedPayload)
|
|
355
|
+
};
|
|
356
|
+
(_a = this.peer.webViewRef.current) === null || _a === void 0 ? void 0 : _a.postMessage(JSON.stringify(newObject(messageData)));
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
this.debug("[".concat(this.peer.type, "] Emit error:"), error);
|
|
360
|
+
this.fire('error', {
|
|
361
|
+
type: 'EMIT_ERROR',
|
|
362
|
+
event: _event,
|
|
363
|
+
error: error instanceof Error ? error.message : String(error)
|
|
364
|
+
});
|
|
365
|
+
// Call acknowledgment with error if provided
|
|
366
|
+
typeof fn === 'function'
|
|
367
|
+
&& fn(error instanceof Error ? error.message : String(error));
|
|
368
|
+
}
|
|
369
|
+
return this;
|
|
370
|
+
};
|
|
371
|
+
WIO.prototype.on = function (_event, fn) {
|
|
372
|
+
// Add Event listener
|
|
373
|
+
if (!this.Events[_event])
|
|
374
|
+
this.Events[_event] = [];
|
|
375
|
+
this.Events[_event].push(fn);
|
|
376
|
+
this.debug("[".concat(this.peer.type, "] New <").concat(_event, "> listener on"));
|
|
377
|
+
return this;
|
|
378
|
+
};
|
|
379
|
+
WIO.prototype.once = function (_event, fn) {
|
|
380
|
+
// Add Once Event listener
|
|
381
|
+
_event += '--@once';
|
|
382
|
+
if (!this.Events[_event])
|
|
383
|
+
this.Events[_event] = [];
|
|
384
|
+
this.Events[_event].push(fn);
|
|
385
|
+
this.debug("[".concat(this.peer.type, "] New <").concat(_event, " once> listener on"));
|
|
386
|
+
return this;
|
|
387
|
+
};
|
|
388
|
+
WIO.prototype.off = function (_event, fn) {
|
|
389
|
+
// Remove Event listener
|
|
390
|
+
if (fn && this.Events[_event]) {
|
|
391
|
+
// Remove specific listener if provided
|
|
392
|
+
var index = this.Events[_event].indexOf(fn);
|
|
393
|
+
if (index > -1) {
|
|
394
|
+
this.Events[_event].splice(index, 1);
|
|
395
|
+
// Remove event array if empty
|
|
396
|
+
if (this.Events[_event].length === 0)
|
|
397
|
+
delete this.Events[_event];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Remove all listeners for event
|
|
401
|
+
else
|
|
402
|
+
delete this.Events[_event];
|
|
403
|
+
typeof fn == 'function' && fn();
|
|
404
|
+
this.debug("[".concat(this.peer.type, "] <").concat(_event, "> listener off"));
|
|
405
|
+
return this;
|
|
406
|
+
};
|
|
407
|
+
WIO.prototype.removeListeners = function (fn) {
|
|
408
|
+
// Clear all event listeners
|
|
409
|
+
this.Events = {};
|
|
410
|
+
typeof fn == 'function' && fn();
|
|
411
|
+
this.debug("[".concat(this.peer.type, "] All listeners removed"));
|
|
412
|
+
return this;
|
|
413
|
+
};
|
|
414
|
+
WIO.prototype.emitAsync = function (_event, payload, timeout) {
|
|
415
|
+
var _this = this;
|
|
416
|
+
if (timeout === void 0) { timeout = 5000; }
|
|
417
|
+
return new Promise(function (resolve, reject) {
|
|
418
|
+
var timeoutId = setTimeout(function () {
|
|
419
|
+
reject(new Error("Event '".concat(_event, "' acknowledgment timeout after ").concat(timeout, "ms")));
|
|
420
|
+
}, timeout);
|
|
421
|
+
try {
|
|
422
|
+
_this.emit(_event, payload, function (error) {
|
|
423
|
+
var args = [];
|
|
424
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
425
|
+
args[_i - 1] = arguments[_i];
|
|
426
|
+
}
|
|
427
|
+
clearTimeout(timeoutId);
|
|
428
|
+
error
|
|
429
|
+
? reject(new Error(typeof error === 'string' ? error : 'Ack error'))
|
|
430
|
+
: resolve(args.length === 0 ? undefined : args.length === 1 ? args[0] : args);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
clearTimeout(timeoutId);
|
|
435
|
+
reject(error);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
WIO.prototype.onceAsync = function (_event) {
|
|
440
|
+
var _this = this;
|
|
441
|
+
return new Promise(function (resolve) { return _this.once(_event, resolve); });
|
|
442
|
+
};
|
|
443
|
+
WIO.prototype.connectAsync = function (timeout) {
|
|
444
|
+
var _this = this;
|
|
445
|
+
return new Promise(function (resolve, reject) {
|
|
446
|
+
if (_this.isConnected())
|
|
447
|
+
return resolve();
|
|
448
|
+
var timeoutId = setTimeout(function () {
|
|
449
|
+
_this.off('connect', connectHandler);
|
|
450
|
+
reject(new Error('Connection timeout'));
|
|
451
|
+
}, timeout || _this.options.connectionTimeout);
|
|
452
|
+
var connectHandler = function () {
|
|
453
|
+
clearTimeout(timeoutId);
|
|
454
|
+
resolve();
|
|
455
|
+
};
|
|
456
|
+
_this.once('connect', connectHandler);
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
// Clean up all resources
|
|
460
|
+
WIO.prototype.cleanup = function () {
|
|
461
|
+
this.stopHeartbeat();
|
|
462
|
+
if (this.reconnectTimer) {
|
|
463
|
+
clearTimeout(this.reconnectTimer);
|
|
464
|
+
this.reconnectTimer = undefined;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
WIO.prototype.disconnect = function (fn) {
|
|
468
|
+
// Cleanup on disconnect
|
|
469
|
+
this.cleanup();
|
|
470
|
+
this.peer.connected = false;
|
|
471
|
+
this.peer.webViewRef = undefined;
|
|
472
|
+
this.peer.origin = undefined;
|
|
473
|
+
this.peer.lastHeartbeat = undefined;
|
|
474
|
+
this.messageQueue = [];
|
|
475
|
+
this.messageRateTracker = [];
|
|
476
|
+
this.reconnectAttempts = 0;
|
|
477
|
+
this.removeListeners();
|
|
478
|
+
typeof fn == 'function' && fn();
|
|
479
|
+
this.debug("[".concat(this.peer.type, "] Disconnected"));
|
|
480
|
+
return this;
|
|
481
|
+
};
|
|
482
|
+
// Get connection statistics
|
|
483
|
+
WIO.prototype.getStats = function () {
|
|
484
|
+
return {
|
|
485
|
+
connected: this.isConnected(),
|
|
486
|
+
peerType: this.peer.type,
|
|
487
|
+
origin: this.peer.origin,
|
|
488
|
+
lastHeartbeat: this.peer.lastHeartbeat,
|
|
489
|
+
queuedMessages: this.messageQueue.length,
|
|
490
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
491
|
+
activeListeners: Object.keys(this.Events).length,
|
|
492
|
+
messageRate: this.messageRateTracker.length
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
// Clear message queue manually
|
|
496
|
+
WIO.prototype.clearQueue = function () {
|
|
497
|
+
var queueSize = this.messageQueue.length;
|
|
498
|
+
this.messageQueue = [];
|
|
499
|
+
this.debug("[".concat(this.peer.type, "] Cleared ").concat(queueSize, " queued messages"));
|
|
500
|
+
return this;
|
|
501
|
+
};
|
|
502
|
+
/**
|
|
503
|
+
* Get injected JavaScript for WebView
|
|
504
|
+
* Sets up the EMBEDDED side of the bridge
|
|
505
|
+
*/
|
|
506
|
+
WIO.prototype.getInjectedJavaScript = function () {
|
|
507
|
+
return "\n (function() {\n const RESERVED_EVENTS = ['ping', 'pong', '__heartbeat', '__heartbeat_response'];\n \n window._wio = {\n type: 'EMBEDDED',\n connected: false,\n Events: {},\n messageQueue: [],\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( !this.Events[_event] && !this.Events[_event + '--@once'] ) return;\n \n const ackFn = cid\n ? ( error, ...args ) => {\n this.emit( _event + '--' + cid + '--@ack', { error: error || false, args } );\n }\n : undefined;\n \n let listeners = [];\n if( this.Events[_event + '--@once'] ){\n _event += '--@once';\n listeners = this.Events[_event];\n delete this.Events[_event];\n }\n else listeners = this.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( !this.connected && !RESERVED_EVENTS.includes(_event) ){\n this.messageQueue.push({ _event, payload, fn, timestamp: Date.now() });\n return;\n }\n \n try {\n let cid;\n if( typeof fn === 'function' ){\n cid = this.ackId();\n this.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 };\n \n window.ReactNativeWebView.postMessage( JSON.stringify( messageData ) );\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( !this.Events[_event] ) this.Events[_event] = [];\n this.Events[_event].push( fn );\n },\n \n once: function( _event, fn ){\n _event += '--@once';\n if( !this.Events[_event] ) this.Events[_event] = [];\n this.Events[_event].push( fn );\n },\n \n off: function( _event, fn ){\n if( fn && this.Events[_event] ){\n const index = this.Events[_event].indexOf( fn );\n if( index > -1 ){\n this.Events[_event].splice( index, 1 );\n if( this.Events[_event].length === 0 ) delete this.Events[_event];\n }\n }\n else delete this.Events[_event];\n },\n \n processMessageQueue: function(){\n if( !this.connected || this.messageQueue.length === 0 ) return;\n \n const queue = [...this.messageQueue];\n this.messageQueue = [];\n \n queue.forEach( msg => {\n try { this.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 } = data;\n \n if( _event === '__heartbeat_response' ) return;\n \n if( _event === '__heartbeat' ){\n this.emit('__heartbeat_response', { timestamp: Date.now() });\n return;\n }\n \n if( _event === 'ping' ){\n this.emit('pong');\n this.connected = true;\n this.processMessageQueue();\n return;\n }\n \n this.fire( _event, payload, cid );\n }\n };\n \n // Listen to messages from React Native\n window.addEventListener('message', function( event ){\n try {\n const message = JSON.parse( 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 = JSON.parse( event.data );\n window._wio.handleMessage( message );\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error); }\n });\n }\n \n true;\n })();\n ";
|
|
508
|
+
};
|
|
509
|
+
return WIO;
|
|
510
|
+
}());
|
|
511
|
+
exports.default = WIO;
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webview.io",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"private": false,
|
|
8
|
+
"scripts": {
|
|
9
|
+
"compile": "rimraf ./dist && tsc",
|
|
10
|
+
"test": "yarn run compile && yarn run test:types && yarn run test:unit",
|
|
11
|
+
"test:types": "tsd",
|
|
12
|
+
"test:unit": "nyc mocha --require ts-node/register --reporter spec --slow 200 --bail --timeout 10000 test/webview.io.ts",
|
|
13
|
+
"prepack": "yarn run compile"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@types/node": "^24.6.0",
|
|
17
|
+
"events": "^3.3.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=16.8.0",
|
|
21
|
+
"react-native-webview": ">=11.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/mocha": "^9.1.0",
|
|
25
|
+
"@types/react": "^18.0.0",
|
|
26
|
+
"expect.js": "^0.3.1",
|
|
27
|
+
"mocha": "^9.2.0",
|
|
28
|
+
"nyc": "^15.1.0",
|
|
29
|
+
"react-native-webview": "^13.16.0",
|
|
30
|
+
"rimraf": "^3.0.2",
|
|
31
|
+
"ts-node": "^10.9.0",
|
|
32
|
+
"tsd": "^0.24.1",
|
|
33
|
+
"typescript": "^4.5.5"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist/",
|
|
37
|
+
"src/",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"directories": {
|
|
41
|
+
"example": "example/",
|
|
42
|
+
"test": "test/"
|
|
43
|
+
},
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git://github.com/fabrice8/webview.io"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"react-native",
|
|
51
|
+
"webview",
|
|
52
|
+
"bridge",
|
|
53
|
+
"communication",
|
|
54
|
+
"realtime",
|
|
55
|
+
"events",
|
|
56
|
+
"mobile",
|
|
57
|
+
"cross-origin",
|
|
58
|
+
"secure",
|
|
59
|
+
"async",
|
|
60
|
+
"promise",
|
|
61
|
+
"react-native-webview",
|
|
62
|
+
"messaging",
|
|
63
|
+
"io"
|
|
64
|
+
],
|
|
65
|
+
"author": "Fabrice K.E.M",
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=12.0.0"
|
|
68
|
+
}
|
|
69
|
+
}
|