@pulseboard/react-native 0.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/PulseBoardSDK.podspec +24 -0
- package/README.md +27 -0
- package/android/build.gradle +46 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/pulseboard/PulseBoardDeviceModule.kt +81 -0
- package/android/src/main/java/com/pulseboard/PulseBoardDevicePackage.kt +16 -0
- package/android/src/main/java/com/pulseboard/PulseBoardNetworkModule.kt +81 -0
- package/android/src/main/java/com/pulseboard/PulseBoardNetworkPackage.kt +16 -0
- package/android/src/main/java/com/pulseboard/PulseBoardPackage.kt +19 -0
- package/dist/index.d.mts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +741 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +739 -0
- package/dist/index.mjs.map +1 -0
- package/ios/PulseBoardDevice.mm +10 -0
- package/ios/PulseBoardDevice.swift +62 -0
- package/ios/PulseBoardNetwork.mm +10 -0
- package/ios/PulseBoardNetwork.swift +84 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactNative = require('react-native');
|
|
4
|
+
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
7
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// node_modules/promise/setimmediate/core.js
|
|
11
|
+
var require_core = __commonJS({
|
|
12
|
+
"node_modules/promise/setimmediate/core.js"(exports$1, module) {
|
|
13
|
+
function noop() {
|
|
14
|
+
}
|
|
15
|
+
var LAST_ERROR = null;
|
|
16
|
+
var IS_ERROR = {};
|
|
17
|
+
function getThen(obj) {
|
|
18
|
+
try {
|
|
19
|
+
return obj.then;
|
|
20
|
+
} catch (ex) {
|
|
21
|
+
LAST_ERROR = ex;
|
|
22
|
+
return IS_ERROR;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function tryCallOne(fn, a) {
|
|
26
|
+
try {
|
|
27
|
+
return fn(a);
|
|
28
|
+
} catch (ex) {
|
|
29
|
+
LAST_ERROR = ex;
|
|
30
|
+
return IS_ERROR;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function tryCallTwo(fn, a, b) {
|
|
34
|
+
try {
|
|
35
|
+
fn(a, b);
|
|
36
|
+
} catch (ex) {
|
|
37
|
+
LAST_ERROR = ex;
|
|
38
|
+
return IS_ERROR;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
module.exports = Promise2;
|
|
42
|
+
function Promise2(fn) {
|
|
43
|
+
if (typeof this !== "object") {
|
|
44
|
+
throw new TypeError("Promises must be constructed via new");
|
|
45
|
+
}
|
|
46
|
+
if (typeof fn !== "function") {
|
|
47
|
+
throw new TypeError("Promise constructor's argument is not a function");
|
|
48
|
+
}
|
|
49
|
+
this._x = 0;
|
|
50
|
+
this._y = 0;
|
|
51
|
+
this._z = null;
|
|
52
|
+
this._A = null;
|
|
53
|
+
if (fn === noop) return;
|
|
54
|
+
doResolve(fn, this);
|
|
55
|
+
}
|
|
56
|
+
Promise2._B = null;
|
|
57
|
+
Promise2._C = null;
|
|
58
|
+
Promise2._D = noop;
|
|
59
|
+
Promise2.prototype.then = function(onFulfilled, onRejected) {
|
|
60
|
+
if (this.constructor !== Promise2) {
|
|
61
|
+
return safeThen(this, onFulfilled, onRejected);
|
|
62
|
+
}
|
|
63
|
+
var res = new Promise2(noop);
|
|
64
|
+
handle(this, new Handler(onFulfilled, onRejected, res));
|
|
65
|
+
return res;
|
|
66
|
+
};
|
|
67
|
+
function safeThen(self, onFulfilled, onRejected) {
|
|
68
|
+
return new self.constructor(function(resolve2, reject2) {
|
|
69
|
+
var res = new Promise2(noop);
|
|
70
|
+
res.then(resolve2, reject2);
|
|
71
|
+
handle(self, new Handler(onFulfilled, onRejected, res));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function handle(self, deferred) {
|
|
75
|
+
while (self._y === 3) {
|
|
76
|
+
self = self._z;
|
|
77
|
+
}
|
|
78
|
+
if (Promise2._B) {
|
|
79
|
+
Promise2._B(self);
|
|
80
|
+
}
|
|
81
|
+
if (self._y === 0) {
|
|
82
|
+
if (self._x === 0) {
|
|
83
|
+
self._x = 1;
|
|
84
|
+
self._A = deferred;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (self._x === 1) {
|
|
88
|
+
self._x = 2;
|
|
89
|
+
self._A = [self._A, deferred];
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
self._A.push(deferred);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
handleResolved(self, deferred);
|
|
96
|
+
}
|
|
97
|
+
function handleResolved(self, deferred) {
|
|
98
|
+
setImmediate(function() {
|
|
99
|
+
var cb = self._y === 1 ? deferred.onFulfilled : deferred.onRejected;
|
|
100
|
+
if (cb === null) {
|
|
101
|
+
if (self._y === 1) {
|
|
102
|
+
resolve(deferred.promise, self._z);
|
|
103
|
+
} else {
|
|
104
|
+
reject(deferred.promise, self._z);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
var ret = tryCallOne(cb, self._z);
|
|
109
|
+
if (ret === IS_ERROR) {
|
|
110
|
+
reject(deferred.promise, LAST_ERROR);
|
|
111
|
+
} else {
|
|
112
|
+
resolve(deferred.promise, ret);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function resolve(self, newValue) {
|
|
117
|
+
if (newValue === self) {
|
|
118
|
+
return reject(
|
|
119
|
+
self,
|
|
120
|
+
new TypeError("A promise cannot be resolved with itself.")
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (newValue && (typeof newValue === "object" || typeof newValue === "function")) {
|
|
124
|
+
var then = getThen(newValue);
|
|
125
|
+
if (then === IS_ERROR) {
|
|
126
|
+
return reject(self, LAST_ERROR);
|
|
127
|
+
}
|
|
128
|
+
if (then === self.then && newValue instanceof Promise2) {
|
|
129
|
+
self._y = 3;
|
|
130
|
+
self._z = newValue;
|
|
131
|
+
finale(self);
|
|
132
|
+
return;
|
|
133
|
+
} else if (typeof then === "function") {
|
|
134
|
+
doResolve(then.bind(newValue), self);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
self._y = 1;
|
|
139
|
+
self._z = newValue;
|
|
140
|
+
finale(self);
|
|
141
|
+
}
|
|
142
|
+
function reject(self, newValue) {
|
|
143
|
+
self._y = 2;
|
|
144
|
+
self._z = newValue;
|
|
145
|
+
if (Promise2._C) {
|
|
146
|
+
Promise2._C(self, newValue);
|
|
147
|
+
}
|
|
148
|
+
finale(self);
|
|
149
|
+
}
|
|
150
|
+
function finale(self) {
|
|
151
|
+
if (self._x === 1) {
|
|
152
|
+
handle(self, self._A);
|
|
153
|
+
self._A = null;
|
|
154
|
+
}
|
|
155
|
+
if (self._x === 2) {
|
|
156
|
+
for (var i = 0; i < self._A.length; i++) {
|
|
157
|
+
handle(self, self._A[i]);
|
|
158
|
+
}
|
|
159
|
+
self._A = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function Handler(onFulfilled, onRejected, promise) {
|
|
163
|
+
this.onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;
|
|
164
|
+
this.onRejected = typeof onRejected === "function" ? onRejected : null;
|
|
165
|
+
this.promise = promise;
|
|
166
|
+
}
|
|
167
|
+
function doResolve(fn, promise) {
|
|
168
|
+
var done = false;
|
|
169
|
+
var res = tryCallTwo(fn, function(value) {
|
|
170
|
+
if (done) return;
|
|
171
|
+
done = true;
|
|
172
|
+
resolve(promise, value);
|
|
173
|
+
}, function(reason) {
|
|
174
|
+
if (done) return;
|
|
175
|
+
done = true;
|
|
176
|
+
reject(promise, reason);
|
|
177
|
+
});
|
|
178
|
+
if (!done && res === IS_ERROR) {
|
|
179
|
+
done = true;
|
|
180
|
+
reject(promise, LAST_ERROR);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// node_modules/promise/setimmediate/rejection-tracking.js
|
|
187
|
+
var require_rejection_tracking = __commonJS({
|
|
188
|
+
"node_modules/promise/setimmediate/rejection-tracking.js"(exports$1) {
|
|
189
|
+
var Promise2 = require_core();
|
|
190
|
+
var DEFAULT_WHITELIST = [
|
|
191
|
+
ReferenceError,
|
|
192
|
+
TypeError,
|
|
193
|
+
RangeError
|
|
194
|
+
];
|
|
195
|
+
var enabled = false;
|
|
196
|
+
exports$1.disable = disable;
|
|
197
|
+
function disable() {
|
|
198
|
+
enabled = false;
|
|
199
|
+
Promise2._B = null;
|
|
200
|
+
Promise2._C = null;
|
|
201
|
+
}
|
|
202
|
+
exports$1.enable = enable;
|
|
203
|
+
function enable(options) {
|
|
204
|
+
options = options || {};
|
|
205
|
+
if (enabled) disable();
|
|
206
|
+
enabled = true;
|
|
207
|
+
var id = 0;
|
|
208
|
+
var displayId = 0;
|
|
209
|
+
var rejections = {};
|
|
210
|
+
Promise2._B = function(promise) {
|
|
211
|
+
if (promise._y === 2 && // IS REJECTED
|
|
212
|
+
rejections[promise._E]) {
|
|
213
|
+
if (rejections[promise._E].logged) {
|
|
214
|
+
onHandled(promise._E);
|
|
215
|
+
} else {
|
|
216
|
+
clearTimeout(rejections[promise._E].timeout);
|
|
217
|
+
}
|
|
218
|
+
delete rejections[promise._E];
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
Promise2._C = function(promise, err) {
|
|
222
|
+
if (promise._x === 0) {
|
|
223
|
+
promise._E = id++;
|
|
224
|
+
rejections[promise._E] = {
|
|
225
|
+
displayId: null,
|
|
226
|
+
error: err,
|
|
227
|
+
timeout: setTimeout(
|
|
228
|
+
onUnhandled.bind(null, promise._E),
|
|
229
|
+
// For reference errors and type errors, this almost always
|
|
230
|
+
// means the programmer made a mistake, so log them after just
|
|
231
|
+
// 100ms
|
|
232
|
+
// otherwise, wait 2 seconds to see if they get handled
|
|
233
|
+
matchWhitelist(err, DEFAULT_WHITELIST) ? 100 : 2e3
|
|
234
|
+
),
|
|
235
|
+
logged: false
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
function onUnhandled(id2) {
|
|
240
|
+
if (options.allRejections || matchWhitelist(
|
|
241
|
+
rejections[id2].error,
|
|
242
|
+
options.whitelist || DEFAULT_WHITELIST
|
|
243
|
+
)) {
|
|
244
|
+
rejections[id2].displayId = displayId++;
|
|
245
|
+
if (options.onUnhandled) {
|
|
246
|
+
rejections[id2].logged = true;
|
|
247
|
+
options.onUnhandled(
|
|
248
|
+
rejections[id2].displayId,
|
|
249
|
+
rejections[id2].error
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
rejections[id2].logged = true;
|
|
253
|
+
logError(
|
|
254
|
+
rejections[id2].displayId,
|
|
255
|
+
rejections[id2].error
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function onHandled(id2) {
|
|
261
|
+
if (rejections[id2].logged) {
|
|
262
|
+
if (options.onHandled) {
|
|
263
|
+
options.onHandled(rejections[id2].displayId, rejections[id2].error);
|
|
264
|
+
} else if (!rejections[id2].onUnhandled) {
|
|
265
|
+
console.warn(
|
|
266
|
+
"Promise Rejection Handled (id: " + rejections[id2].displayId + "):"
|
|
267
|
+
);
|
|
268
|
+
console.warn(
|
|
269
|
+
' This means you can ignore any previous messages of the form "Possible Unhandled Promise Rejection" with id ' + rejections[id2].displayId + "."
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function logError(id, error) {
|
|
276
|
+
console.warn("Possible Unhandled Promise Rejection (id: " + id + "):");
|
|
277
|
+
var errStr = (error && (error.stack || error)) + "";
|
|
278
|
+
errStr.split("\n").forEach(function(line) {
|
|
279
|
+
console.warn(" " + line);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function matchWhitelist(error, list) {
|
|
283
|
+
return list.some(function(cls) {
|
|
284
|
+
return error instanceof cls;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// src/client.ts
|
|
291
|
+
var PulseBoardClient = class {
|
|
292
|
+
constructor(host, debug = false) {
|
|
293
|
+
this.host = host.replace(/\/$/, "");
|
|
294
|
+
this.debug = debug;
|
|
295
|
+
}
|
|
296
|
+
// ─── Event Ingest ─────────────────────────────────────────────────
|
|
297
|
+
async send(event) {
|
|
298
|
+
try {
|
|
299
|
+
const response = await fetch(`${this.host}/ingest`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: { "Content-Type": "application/json" },
|
|
302
|
+
body: JSON.stringify(event)
|
|
303
|
+
});
|
|
304
|
+
if (!response.ok) {
|
|
305
|
+
this.log(
|
|
306
|
+
`Failed to send event: ${response.status} ${response.statusText}`
|
|
307
|
+
);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
this.log(`Event sent: ${event.type} \u2014 ${event.name}`);
|
|
311
|
+
return true;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
this.log(`Network error sending event: ${err}`);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async sendBatch(events) {
|
|
318
|
+
const chunks = this.chunk(events, 5);
|
|
319
|
+
for (const chunk of chunks) {
|
|
320
|
+
await Promise.all(chunk.map((e) => this.send(e)));
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
// ─── Analytics Ingest ─────────────────────────────────────────────
|
|
325
|
+
async sendAnalytics(type, payload) {
|
|
326
|
+
try {
|
|
327
|
+
const response = await fetch(`${this.host}/analytics/${type}`, {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: { "Content-Type": "application/json" },
|
|
330
|
+
body: JSON.stringify(payload)
|
|
331
|
+
});
|
|
332
|
+
if (!response.ok) {
|
|
333
|
+
this.log(
|
|
334
|
+
`Failed to send analytics: ${response.status} ${response.statusText}`
|
|
335
|
+
);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
this.log(`Analytics sent: ${type}`);
|
|
339
|
+
return true;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
this.log(`Network error sending analytics: ${err}`);
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// ─── Private ──────────────────────────────────────────────────────
|
|
346
|
+
chunk(arr, size) {
|
|
347
|
+
return Array.from(
|
|
348
|
+
{ length: Math.ceil(arr.length / size) },
|
|
349
|
+
(_, i) => arr.slice(i * size, i * size + size)
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
log(message) {
|
|
353
|
+
if (this.debug) {
|
|
354
|
+
console.log(`[PulseBoard] ${message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// src/queue.ts
|
|
360
|
+
var EventQueue = class {
|
|
361
|
+
constructor(maxSize = 100) {
|
|
362
|
+
this.queue = [];
|
|
363
|
+
this.maxSize = maxSize;
|
|
364
|
+
}
|
|
365
|
+
enqueue(event) {
|
|
366
|
+
if (this.queue.length >= this.maxSize) {
|
|
367
|
+
this.queue.shift();
|
|
368
|
+
}
|
|
369
|
+
this.queue.push(event);
|
|
370
|
+
}
|
|
371
|
+
dequeue(count) {
|
|
372
|
+
return this.queue.splice(0, count);
|
|
373
|
+
}
|
|
374
|
+
get size() {
|
|
375
|
+
return this.queue.length;
|
|
376
|
+
}
|
|
377
|
+
get isEmpty() {
|
|
378
|
+
return this.queue.length === 0;
|
|
379
|
+
}
|
|
380
|
+
clear() {
|
|
381
|
+
this.queue = [];
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// src/auto-capture.ts
|
|
386
|
+
var AutoCapture = class {
|
|
387
|
+
constructor(handler) {
|
|
388
|
+
this.attached = false;
|
|
389
|
+
this.previousHandler = null;
|
|
390
|
+
this.handler = handler;
|
|
391
|
+
}
|
|
392
|
+
attach() {
|
|
393
|
+
if (this.attached) return;
|
|
394
|
+
this.previousHandler = ErrorUtils.getGlobalHandler();
|
|
395
|
+
ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
396
|
+
this.handler("uncaughtException", error, { isFatal: isFatal ?? false });
|
|
397
|
+
if (this.previousHandler) {
|
|
398
|
+
this.previousHandler(error, isFatal);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
const tracking = require_rejection_tracking();
|
|
402
|
+
tracking.enable({
|
|
403
|
+
allRejections: true,
|
|
404
|
+
onUnhandled: (_id, error) => {
|
|
405
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
406
|
+
this.handler("unhandledRejection", err, { fatal: false });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
this.attached = true;
|
|
410
|
+
}
|
|
411
|
+
detach() {
|
|
412
|
+
if (!this.attached) return;
|
|
413
|
+
if (this.previousHandler) {
|
|
414
|
+
ErrorUtils.setGlobalHandler(this.previousHandler);
|
|
415
|
+
this.previousHandler = null;
|
|
416
|
+
}
|
|
417
|
+
this.attached = false;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
var NativePulseBoardDevice_default = reactNative.TurboModuleRegistry.getEnforcing("PulseBoardDevice");
|
|
421
|
+
var NativePulseBoardNetwork_default = reactNative.TurboModuleRegistry.getEnforcing("PulseBoardNetwork");
|
|
422
|
+
|
|
423
|
+
// src/context.ts
|
|
424
|
+
function generateSessionId() {
|
|
425
|
+
const timestamp = Date.now().toString(36);
|
|
426
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
427
|
+
return `${timestamp}-${random}`;
|
|
428
|
+
}
|
|
429
|
+
function mapNetworkType(type) {
|
|
430
|
+
switch (type) {
|
|
431
|
+
case "wifi":
|
|
432
|
+
return "wifi";
|
|
433
|
+
case "cellular":
|
|
434
|
+
return "cellular";
|
|
435
|
+
case "offline":
|
|
436
|
+
return "offline";
|
|
437
|
+
default:
|
|
438
|
+
return "unknown";
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function getTimezone() {
|
|
442
|
+
try {
|
|
443
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
444
|
+
} catch {
|
|
445
|
+
return "unknown";
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function getLanguage() {
|
|
449
|
+
try {
|
|
450
|
+
return reactNative.I18nManager.getConstants?.()?.localeIdentifier?.replace("_", "-") ?? Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
|
|
451
|
+
} catch {
|
|
452
|
+
return "unknown";
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
var ContextCollector = class {
|
|
456
|
+
constructor(appContext = {}) {
|
|
457
|
+
this.userContext = {};
|
|
458
|
+
this.appContext = appContext;
|
|
459
|
+
this.sessionContext = {
|
|
460
|
+
sessionId: generateSessionId(),
|
|
461
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
identify(user) {
|
|
465
|
+
this.userContext = { ...this.userContext, ...user };
|
|
466
|
+
}
|
|
467
|
+
clearUser() {
|
|
468
|
+
this.userContext = {};
|
|
469
|
+
}
|
|
470
|
+
async collect() {
|
|
471
|
+
const { width, height } = reactNative.Dimensions.get("window");
|
|
472
|
+
let deviceInfo = null;
|
|
473
|
+
let networkInfo = null;
|
|
474
|
+
try {
|
|
475
|
+
if (NativePulseBoardDevice_default) {
|
|
476
|
+
deviceInfo = await NativePulseBoardDevice_default.getDeviceInfo();
|
|
477
|
+
}
|
|
478
|
+
} catch (e) {
|
|
479
|
+
console.warn("[PulseBoard] Failed to collect device info:", e);
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
if (NativePulseBoardNetwork_default) {
|
|
483
|
+
networkInfo = await NativePulseBoardNetwork_default.getNetworkInfo();
|
|
484
|
+
}
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.warn("[PulseBoard] Failed to collect network info:", e);
|
|
487
|
+
}
|
|
488
|
+
const device = {
|
|
489
|
+
platform: reactNative.Platform.OS === "ios" ? "ios" : "android",
|
|
490
|
+
os: deviceInfo?.os ?? (reactNative.Platform.OS === "ios" ? "iOS" : "Android"),
|
|
491
|
+
osVersion: deviceInfo?.osVersion ?? reactNative.Platform.Version?.toString() ?? "unknown",
|
|
492
|
+
model: deviceInfo?.model ?? "unknown",
|
|
493
|
+
manufacturer: deviceInfo?.manufacturer ?? "unknown",
|
|
494
|
+
brand: deviceInfo?.brand ?? "unknown",
|
|
495
|
+
isTablet: deviceInfo?.isTablet ?? false,
|
|
496
|
+
appVersion: this.appContext.appVersion ?? deviceInfo?.appVersion ?? "unknown",
|
|
497
|
+
buildNumber: this.appContext.buildNumber ?? deviceInfo?.buildNumber ?? "unknown",
|
|
498
|
+
bundleId: deviceInfo?.bundleId ?? "unknown",
|
|
499
|
+
screenWidth: deviceInfo?.screenWidth ?? width,
|
|
500
|
+
screenHeight: deviceInfo?.screenHeight ?? height,
|
|
501
|
+
fontScale: reactNative.Dimensions.get("window").fontScale ?? 1,
|
|
502
|
+
isEmulator: deviceInfo?.isEmulator ?? false,
|
|
503
|
+
language: getLanguage(),
|
|
504
|
+
timezone: getTimezone()
|
|
505
|
+
};
|
|
506
|
+
const network = {
|
|
507
|
+
type: mapNetworkType(networkInfo?.type ?? "unknown"),
|
|
508
|
+
isConnected: networkInfo?.isConnected ?? false,
|
|
509
|
+
isWifiEnabled: networkInfo?.isWifiEnabled ?? false,
|
|
510
|
+
carrier: networkInfo?.carrier ?? "unknown",
|
|
511
|
+
ipAddress: networkInfo?.ipAddress ?? "unknown"
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
app: this.appContext,
|
|
515
|
+
device,
|
|
516
|
+
network,
|
|
517
|
+
session: this.sessionContext,
|
|
518
|
+
user: this.userContext
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/index.ts
|
|
524
|
+
var PulseBoardSDK = class {
|
|
525
|
+
constructor() {
|
|
526
|
+
this.config = null;
|
|
527
|
+
this.client = null;
|
|
528
|
+
this.queue = null;
|
|
529
|
+
this.autoCapture = null;
|
|
530
|
+
this.contextCollector = null;
|
|
531
|
+
this.flushTimer = null;
|
|
532
|
+
this.initialized = false;
|
|
533
|
+
}
|
|
534
|
+
// ─── Public API ───────────────────────────────────────────────────
|
|
535
|
+
init(config) {
|
|
536
|
+
if (this.initialized) {
|
|
537
|
+
this.log("SDK already initialized \u2014 skipping");
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.config = {
|
|
541
|
+
autoCapture: true,
|
|
542
|
+
debug: false,
|
|
543
|
+
flushInterval: 5e3,
|
|
544
|
+
maxQueueSize: 100,
|
|
545
|
+
...config
|
|
546
|
+
};
|
|
547
|
+
this.client = new PulseBoardClient(this.config.host, this.config.debug);
|
|
548
|
+
this.queue = new EventQueue(this.config.maxQueueSize);
|
|
549
|
+
this.contextCollector = new ContextCollector(this.config.app ?? {});
|
|
550
|
+
this.flushTimer = setInterval(
|
|
551
|
+
() => this.flush(),
|
|
552
|
+
this.config.flushInterval
|
|
553
|
+
);
|
|
554
|
+
if (this.config.autoCapture) {
|
|
555
|
+
this.autoCapture = new AutoCapture((name, error, context) => {
|
|
556
|
+
this.captureError(error, { payload: context });
|
|
557
|
+
});
|
|
558
|
+
this.autoCapture.attach();
|
|
559
|
+
}
|
|
560
|
+
this.initialized = true;
|
|
561
|
+
this.log(`Initialized \u2014 host: ${this.config.host}`);
|
|
562
|
+
}
|
|
563
|
+
async getContext() {
|
|
564
|
+
this.assertInitialized("getContext");
|
|
565
|
+
return this.contextCollector.collect();
|
|
566
|
+
}
|
|
567
|
+
identify(user) {
|
|
568
|
+
this.assertInitialized("identify");
|
|
569
|
+
this.contextCollector.identify(user);
|
|
570
|
+
this.log(`User identified: ${JSON.stringify(user)}`);
|
|
571
|
+
}
|
|
572
|
+
clearUser() {
|
|
573
|
+
this.assertInitialized("clearUser");
|
|
574
|
+
this.contextCollector.clearUser();
|
|
575
|
+
}
|
|
576
|
+
track(name, options = {}) {
|
|
577
|
+
this.assertInitialized("track");
|
|
578
|
+
this.buildAndEnqueue(
|
|
579
|
+
"event",
|
|
580
|
+
name,
|
|
581
|
+
options.payload ?? {},
|
|
582
|
+
options.timestamp
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
metric(name, value, options = {}) {
|
|
586
|
+
this.assertInitialized("metric");
|
|
587
|
+
this.buildAndEnqueue(
|
|
588
|
+
"metric",
|
|
589
|
+
name,
|
|
590
|
+
{ value, ...options.payload },
|
|
591
|
+
options.timestamp
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
captureError(error, options = {}) {
|
|
595
|
+
this.assertInitialized("captureError");
|
|
596
|
+
this.buildAndEnqueue("error", error.name ?? "UnknownError", {
|
|
597
|
+
message: error.message,
|
|
598
|
+
stack: error.stack,
|
|
599
|
+
...options.payload
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
// ─── Analytics API ────────────────────────────────────────────────
|
|
603
|
+
startSession() {
|
|
604
|
+
this.assertInitialized("startSession");
|
|
605
|
+
this.contextCollector.collect().then((context) => {
|
|
606
|
+
this.client.sendAnalytics("session", {
|
|
607
|
+
apiKey: this.config.apiKey,
|
|
608
|
+
sessionId: context.session.sessionId,
|
|
609
|
+
startedAt: context.session.startedAt,
|
|
610
|
+
context
|
|
611
|
+
});
|
|
612
|
+
this.log(`Session started: ${context.session.sessionId}`);
|
|
613
|
+
}).catch((err) => {
|
|
614
|
+
this.log(`Failed to start session: ${err}`);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
endSession(duration) {
|
|
618
|
+
this.assertInitialized("endSession");
|
|
619
|
+
this.contextCollector.collect().then((context) => {
|
|
620
|
+
this.client.sendAnalytics("session", {
|
|
621
|
+
apiKey: this.config.apiKey,
|
|
622
|
+
sessionId: context.session.sessionId,
|
|
623
|
+
startedAt: context.session.startedAt,
|
|
624
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
625
|
+
duration,
|
|
626
|
+
context
|
|
627
|
+
});
|
|
628
|
+
this.flush();
|
|
629
|
+
this.log(`Session ended: ${context.session.sessionId}`);
|
|
630
|
+
}).catch((err) => {
|
|
631
|
+
this.log(`Failed to end session: ${err}`);
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
trackScreen(screenName, loadTime) {
|
|
635
|
+
this.assertInitialized("trackScreen");
|
|
636
|
+
this.contextCollector.collect().then((context) => {
|
|
637
|
+
this.client.sendAnalytics("screen-view", {
|
|
638
|
+
apiKey: this.config.apiKey,
|
|
639
|
+
screenName,
|
|
640
|
+
loadTime,
|
|
641
|
+
sessionId: context.session.sessionId,
|
|
642
|
+
context
|
|
643
|
+
});
|
|
644
|
+
this.log(`Screen tracked: ${screenName}`);
|
|
645
|
+
}).catch((err) => {
|
|
646
|
+
this.log(`Failed to track screen: ${err}`);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
trackApiCall(endpoint, httpMethod, statusCode, duration, payloadSize) {
|
|
650
|
+
this.assertInitialized("trackApiCall");
|
|
651
|
+
this.contextCollector.collect().then((context) => {
|
|
652
|
+
this.client.sendAnalytics("api-call", {
|
|
653
|
+
apiKey: this.config.apiKey,
|
|
654
|
+
endpoint,
|
|
655
|
+
httpMethod,
|
|
656
|
+
statusCode,
|
|
657
|
+
duration,
|
|
658
|
+
payloadSize,
|
|
659
|
+
sessionId: context.session.sessionId,
|
|
660
|
+
context
|
|
661
|
+
});
|
|
662
|
+
this.log(`API call tracked: ${httpMethod} ${endpoint} ${statusCode}`);
|
|
663
|
+
}).catch((err) => {
|
|
664
|
+
this.log(`Failed to track API call: ${err}`);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
trackCrash(error, isFatal = false) {
|
|
668
|
+
this.assertInitialized("trackCrash");
|
|
669
|
+
this.contextCollector.collect().then((context) => {
|
|
670
|
+
this.client.sendAnalytics("crash", {
|
|
671
|
+
apiKey: this.config.apiKey,
|
|
672
|
+
errorName: error.name,
|
|
673
|
+
errorMessage: error.message,
|
|
674
|
+
stackTrace: error.stack ?? "",
|
|
675
|
+
isFatal,
|
|
676
|
+
sessionId: context.session.sessionId,
|
|
677
|
+
context
|
|
678
|
+
});
|
|
679
|
+
this.log(`Crash tracked: ${error.name} \u2014 fatal: ${isFatal}`);
|
|
680
|
+
}).catch((err) => {
|
|
681
|
+
this.log(`Failed to track crash: ${err}`);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// ─── Flush & Destroy ─────────────────────────────────────────────
|
|
685
|
+
async flush() {
|
|
686
|
+
if (!this.queue || this.queue.isEmpty) return;
|
|
687
|
+
if (!this.client) return;
|
|
688
|
+
const events = this.queue.dequeue(10);
|
|
689
|
+
this.log(`Flushing ${events.length} event(s)`);
|
|
690
|
+
await this.client.sendBatch(events);
|
|
691
|
+
}
|
|
692
|
+
destroy() {
|
|
693
|
+
if (this.flushTimer) {
|
|
694
|
+
clearInterval(this.flushTimer);
|
|
695
|
+
this.flushTimer = null;
|
|
696
|
+
}
|
|
697
|
+
this.autoCapture?.detach();
|
|
698
|
+
this.queue?.clear();
|
|
699
|
+
this.initialized = false;
|
|
700
|
+
this.config = null;
|
|
701
|
+
this.client = null;
|
|
702
|
+
this.queue = null;
|
|
703
|
+
this.contextCollector = null;
|
|
704
|
+
this.log("SDK destroyed");
|
|
705
|
+
}
|
|
706
|
+
// ─── Private ─────────────────────────────────────────────────────
|
|
707
|
+
buildAndEnqueue(type, name, payload, timestamp) {
|
|
708
|
+
this.contextCollector.collect().then((context) => {
|
|
709
|
+
const event = {
|
|
710
|
+
apiKey: this.config.apiKey,
|
|
711
|
+
type,
|
|
712
|
+
name,
|
|
713
|
+
payload,
|
|
714
|
+
timestamp: timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
715
|
+
context
|
|
716
|
+
};
|
|
717
|
+
this.queue.enqueue(event);
|
|
718
|
+
this.log(`Queued: ${type} \u2014 ${name}`);
|
|
719
|
+
if (type === "error") {
|
|
720
|
+
this.flush();
|
|
721
|
+
}
|
|
722
|
+
}).catch((err) => {
|
|
723
|
+
this.log(`Failed to collect context: ${err}`);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
assertInitialized(method) {
|
|
727
|
+
if (!this.initialized) {
|
|
728
|
+
throw new Error(`PulseBoard.${method}() called before PulseBoard.init()`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
log(message) {
|
|
732
|
+
if (this.config?.debug) {
|
|
733
|
+
console.log(`[PulseBoard] ${message}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
var PulseBoard = new PulseBoardSDK();
|
|
738
|
+
|
|
739
|
+
exports.PulseBoard = PulseBoard;
|
|
740
|
+
//# sourceMappingURL=index.js.map
|
|
741
|
+
//# sourceMappingURL=index.js.map
|