@buoy-gg/notifications 7.0.41 → 7.0.43
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/lib/commonjs/components/LocalNotificationTest.js +1 -185
- package/lib/commonjs/components/NotificationTestView.js +1 -329
- package/lib/commonjs/components/NotificationToolView.js +1 -673
- package/lib/commonjs/components/notificationStyles.js +1 -292
- package/lib/commonjs/expo.js +1 -247
- package/lib/commonjs/index.js +1 -148
- package/lib/commonjs/json.js +1 -58
- package/lib/commonjs/payload.js +1 -68
- package/lib/commonjs/preset.js +1 -56
- package/lib/commonjs/preview.js +1 -18
- package/lib/commonjs/recorder.js +1 -339
- package/lib/commonjs/runtime.js +1 -110
- package/lib/commonjs/storage.js +1 -61
- package/lib/commonjs/types.js +0 -1
- package/lib/module/components/LocalNotificationTest.js +1 -180
- package/lib/module/components/NotificationTestView.js +1 -324
- package/lib/module/components/NotificationToolView.js +1 -668
- package/lib/module/components/notificationStyles.js +1 -288
- package/lib/module/expo.js +1 -241
- package/lib/module/index.js +1 -13
- package/lib/module/json.js +1 -51
- package/lib/module/payload.js +1 -63
- package/lib/module/preset.js +1 -50
- package/lib/module/preview.js +1 -14
- package/lib/module/recorder.js +1 -332
- package/lib/module/runtime.js +1 -103
- package/lib/module/storage.js +1 -56
- package/lib/module/types.js +0 -1
- package/package.json +2 -2
- package/lib/commonjs/components/LocalNotificationTest.js.map +0 -1
- package/lib/commonjs/components/NotificationTestView.js.map +0 -1
- package/lib/commonjs/components/NotificationToolView.js.map +0 -1
- package/lib/commonjs/components/notificationStyles.js.map +0 -1
- package/lib/commonjs/expo.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/json.js.map +0 -1
- package/lib/commonjs/payload.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/preview.js.map +0 -1
- package/lib/commonjs/recorder.js.map +0 -1
- package/lib/commonjs/runtime.js.map +0 -1
- package/lib/commonjs/storage.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/components/LocalNotificationTest.js.map +0 -1
- package/lib/module/components/NotificationTestView.js.map +0 -1
- package/lib/module/components/NotificationToolView.js.map +0 -1
- package/lib/module/components/notificationStyles.js.map +0 -1
- package/lib/module/expo.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/json.js.map +0 -1
- package/lib/module/payload.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/preview.js.map +0 -1
- package/lib/module/recorder.js.map +0 -1
- package/lib/module/runtime.js.map +0 -1
- package/lib/module/storage.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/components/LocalNotificationTest.d.ts.map +0 -1
- package/lib/typescript/components/NotificationTestView.d.ts.map +0 -1
- package/lib/typescript/components/NotificationToolView.d.ts.map +0 -1
- package/lib/typescript/components/notificationStyles.d.ts.map +0 -1
- package/lib/typescript/expo.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/json.d.ts.map +0 -1
- package/lib/typescript/payload.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/preview.d.ts.map +0 -1
- package/lib/typescript/recorder.d.ts.map +0 -1
- package/lib/typescript/runtime.d.ts.map +0 -1
- package/lib/typescript/storage.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
package/lib/commonjs/recorder.js
CHANGED
|
@@ -1,339 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.NotificationRecorder = void 0;
|
|
7
|
-
exports.expoMetadata = expoMetadata;
|
|
8
|
-
exports.responseKey = responseKey;
|
|
9
|
-
var _json = require("./json");
|
|
10
|
-
const PREFIX = "buoy.notifications.v1.";
|
|
11
|
-
const SESSION = PREFIX + "session";
|
|
12
|
-
const TOKENS = PREFIX + "tokens";
|
|
13
|
-
const EVENT = PREFIX + "event.";
|
|
14
|
-
const MAX_RECORD_BYTES = 16 * 1024;
|
|
15
|
-
const MAX_JOURNAL_BYTES = 512 * 1024;
|
|
16
|
-
const MAX_RECORDS = 200;
|
|
17
|
-
/** Recording runs independently of the desktop's snapshot subscriptions. */
|
|
18
|
-
class NotificationRecorder {
|
|
19
|
-
state = {
|
|
20
|
-
version: 1,
|
|
21
|
-
ready: false,
|
|
22
|
-
provider: null,
|
|
23
|
-
providerVersion: null,
|
|
24
|
-
platform: "unknown",
|
|
25
|
-
appId: null,
|
|
26
|
-
capabilities: [],
|
|
27
|
-
durable: false,
|
|
28
|
-
storageError: null,
|
|
29
|
-
evicted: 0,
|
|
30
|
-
session: null,
|
|
31
|
-
events: [],
|
|
32
|
-
tokens: [],
|
|
33
|
-
permissions: null,
|
|
34
|
-
presented: null
|
|
35
|
-
};
|
|
36
|
-
listeners = new Set();
|
|
37
|
-
sequence = 0;
|
|
38
|
-
queue = Promise.resolve();
|
|
39
|
-
constructor(options = {}) {
|
|
40
|
-
this.options = options;
|
|
41
|
-
this.now = options.now ?? Date.now;
|
|
42
|
-
this.processId = options.processId ?? `${this.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
43
|
-
this.state.durable = !!options.storage && options.durable === true;
|
|
44
|
-
this.ready = this.hydrate();
|
|
45
|
-
this.queue = this.ready;
|
|
46
|
-
}
|
|
47
|
-
getSnapshot = () => this.state;
|
|
48
|
-
subscribe = listener => {
|
|
49
|
-
this.listeners.add(listener);
|
|
50
|
-
return () => {
|
|
51
|
-
this.listeners.delete(listener);
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
update(patch) {
|
|
55
|
-
this.state = {
|
|
56
|
-
...this.state,
|
|
57
|
-
...patch
|
|
58
|
-
};
|
|
59
|
-
for (const listener of this.listeners) {
|
|
60
|
-
try {
|
|
61
|
-
listener();
|
|
62
|
-
} catch {/* Observers cannot interrupt app callbacks. */}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
configure(patch) {
|
|
66
|
-
this.update(patch);
|
|
67
|
-
}
|
|
68
|
-
allowed() {
|
|
69
|
-
return this.options.enabled?.() ?? true;
|
|
70
|
-
}
|
|
71
|
-
storageFailure(error) {
|
|
72
|
-
this.update({
|
|
73
|
-
storageError: error instanceof Error ? error.message : String(error),
|
|
74
|
-
durable: false
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
serial(task) {
|
|
78
|
-
const next = this.queue.then(task);
|
|
79
|
-
this.queue = next.catch(() => {});
|
|
80
|
-
return next;
|
|
81
|
-
}
|
|
82
|
-
async hydrate() {
|
|
83
|
-
if (!this.allowed()) {
|
|
84
|
-
this.update({
|
|
85
|
-
ready: true
|
|
86
|
-
});
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (this.options.durability) {
|
|
90
|
-
try {
|
|
91
|
-
this.update({
|
|
92
|
-
durable: await this.options.durability
|
|
93
|
-
});
|
|
94
|
-
} catch (error) {
|
|
95
|
-
this.storageFailure(error);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const storage = this.options.storage;
|
|
99
|
-
let explicitlyStopped = false;
|
|
100
|
-
if (storage) try {
|
|
101
|
-
const rawSession = await storage.getItem(SESSION);
|
|
102
|
-
explicitlyStopped = rawSession === "null";
|
|
103
|
-
if (rawSession) {
|
|
104
|
-
const s = JSON.parse(rawSession);
|
|
105
|
-
if (s && typeof s.runId === "string" && Number.isFinite(s.expiresAt) && s.expiresAt > this.now()) this.update({
|
|
106
|
-
session: s
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
const rawTokens = await storage.getItem(TOKENS);
|
|
110
|
-
if (rawTokens) {
|
|
111
|
-
const tokens = JSON.parse(rawTokens);
|
|
112
|
-
if (Array.isArray(tokens)) this.update({
|
|
113
|
-
tokens: tokens.filter(t => t && ["apns", "fcm", "expo"].includes(t.type) && typeof t.value === "string").slice(-10)
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
const keys = (await storage.getAllKeys()).filter(key => key.startsWith(EVENT)).sort();
|
|
117
|
-
const records = [];
|
|
118
|
-
for (const key of keys) {
|
|
119
|
-
try {
|
|
120
|
-
const raw = await storage.getItem(key);
|
|
121
|
-
if (!raw || (0, _json.byteLength)(raw) > MAX_RECORD_BYTES) {
|
|
122
|
-
await storage.removeItem(key);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const record = JSON.parse(raw);
|
|
126
|
-
if (typeof record.id === "string" && typeof record.kind === "string" && Number.isFinite(record.capturedAt)) records.push(record);else await storage.removeItem(key);
|
|
127
|
-
} catch {
|
|
128
|
-
await storage.removeItem(key);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
records.sort((a, b) => a.capturedAt - b.capturedAt || a.sequence - b.sequence || a.id.localeCompare(b.id));
|
|
132
|
-
this.update({
|
|
133
|
-
events: records
|
|
134
|
-
});
|
|
135
|
-
await this.prune();
|
|
136
|
-
} catch (error) {
|
|
137
|
-
this.storageFailure(error);
|
|
138
|
-
}
|
|
139
|
-
if (this.options.autoStart && !this.state.session && !explicitlyStopped) {
|
|
140
|
-
const startedAt = this.now();
|
|
141
|
-
const session = {
|
|
142
|
-
runId: this.processId,
|
|
143
|
-
startedAt,
|
|
144
|
-
expiresAt: startedAt + 30 * 60 * 1000
|
|
145
|
-
};
|
|
146
|
-
try {
|
|
147
|
-
await storage?.setItem(SESSION, JSON.stringify(session));
|
|
148
|
-
} catch (error) {
|
|
149
|
-
this.storageFailure(error);
|
|
150
|
-
}
|
|
151
|
-
this.update({
|
|
152
|
-
session
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
this.update({
|
|
156
|
-
ready: true
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
async setCaptureSession(runId, durationMs = 30 * 60 * 1000) {
|
|
160
|
-
if (!this.allowed()) throw new Error("Notification capture is disabled in this build.");
|
|
161
|
-
if (runId !== null && (typeof runId !== "string" || !runId.trim() || runId.length > 128)) throw new Error("runId must contain 1 to 128 characters, or be null to stop capture.");
|
|
162
|
-
if (!Number.isFinite(durationMs) || durationMs < 1000 || durationMs > 60 * 60 * 1000) throw new Error("Capture duration must be between 1 second and 1 hour.");
|
|
163
|
-
return this.serial(async () => {
|
|
164
|
-
const startedAt = this.now();
|
|
165
|
-
const session = runId === null ? null : {
|
|
166
|
-
runId,
|
|
167
|
-
startedAt,
|
|
168
|
-
expiresAt: startedAt + durationMs
|
|
169
|
-
};
|
|
170
|
-
try {
|
|
171
|
-
if (this.options.storage) await this.options.storage.setItem(SESSION, JSON.stringify(session));
|
|
172
|
-
} catch (error) {
|
|
173
|
-
this.storageFailure(error);
|
|
174
|
-
throw new Error("Could not persist the capture session. Keep the app open and check storage.");
|
|
175
|
-
}
|
|
176
|
-
this.update({
|
|
177
|
-
session
|
|
178
|
-
});
|
|
179
|
-
return {
|
|
180
|
-
session,
|
|
181
|
-
durable: this.state.durable
|
|
182
|
-
};
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
record(kind, raw, metadata = {}) {
|
|
186
|
-
const capturedAt = this.now();
|
|
187
|
-
const observedAppState = this.options.appState?.() ?? "unknown";
|
|
188
|
-
const clean = (0, _json.json)(raw);
|
|
189
|
-
return this.serial(async () => {
|
|
190
|
-
if (!this.allowed()) return null;
|
|
191
|
-
const session = this.state.session;
|
|
192
|
-
if (!session || session.expiresAt <= capturedAt) {
|
|
193
|
-
if (session) this.update({
|
|
194
|
-
session: null
|
|
195
|
-
});
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
const clipped = (value, max = 512) => typeof value === "string" ? value.slice(0, max) : null;
|
|
199
|
-
const dedupeKey = clipped(metadata.dedupeKey, 2048);
|
|
200
|
-
if (dedupeKey && this.state.events.some(e => e.dedupeKey === dedupeKey)) return null;
|
|
201
|
-
const record = {
|
|
202
|
-
id: `${this.processId}-${++this.sequence}`,
|
|
203
|
-
processId: this.processId,
|
|
204
|
-
sequence: this.sequence,
|
|
205
|
-
capturedAt,
|
|
206
|
-
runId: session.runId,
|
|
207
|
-
kind,
|
|
208
|
-
provider: clipped(metadata.provider, 64) ?? this.state.provider ?? "app",
|
|
209
|
-
observedAppState,
|
|
210
|
-
notificationId: clipped(metadata.notificationId),
|
|
211
|
-
testId: clipped(metadata.testId),
|
|
212
|
-
title: clipped(metadata.title, 1024),
|
|
213
|
-
actionIdentifier: clipped(metadata.actionIdentifier),
|
|
214
|
-
...(dedupeKey ? {
|
|
215
|
-
dedupeKey
|
|
216
|
-
} : {}),
|
|
217
|
-
raw: clean
|
|
218
|
-
};
|
|
219
|
-
if ((0, _json.byteLength)(JSON.stringify(record)) > MAX_RECORD_BYTES) {
|
|
220
|
-
record.raw = {
|
|
221
|
-
note: "Payload exceeded the 16 KiB capture limit."
|
|
222
|
-
};
|
|
223
|
-
record.truncated = true;
|
|
224
|
-
}
|
|
225
|
-
this.update({
|
|
226
|
-
events: [...this.state.events, record]
|
|
227
|
-
});
|
|
228
|
-
try {
|
|
229
|
-
await this.options.storage?.setItem(EVENT + record.id, JSON.stringify(record));
|
|
230
|
-
} catch (error) {
|
|
231
|
-
this.storageFailure(error);
|
|
232
|
-
}
|
|
233
|
-
// Keep the in-memory bound even when the disk is full or unavailable.
|
|
234
|
-
try {
|
|
235
|
-
await this.prune();
|
|
236
|
-
} catch (error) {
|
|
237
|
-
this.storageFailure(error);
|
|
238
|
-
}
|
|
239
|
-
return record;
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
async prune() {
|
|
243
|
-
const events = [...this.state.events];
|
|
244
|
-
const sizes = events.map(e => (0, _json.byteLength)(JSON.stringify(e)));
|
|
245
|
-
let bytes = sizes.reduce((sum, size) => sum + size, 0);
|
|
246
|
-
const removed = [];
|
|
247
|
-
while (events.length > MAX_RECORDS || bytes > MAX_JOURNAL_BYTES) {
|
|
248
|
-
removed.push(events.shift());
|
|
249
|
-
bytes -= sizes.shift();
|
|
250
|
-
}
|
|
251
|
-
if (removed.length) this.update({
|
|
252
|
-
events,
|
|
253
|
-
evicted: this.state.evicted + removed.length
|
|
254
|
-
});
|
|
255
|
-
for (const event of removed) await this.options.storage?.removeItem(EVENT + event.id);
|
|
256
|
-
}
|
|
257
|
-
async clearCapturedEvents() {
|
|
258
|
-
return this.serial(async () => {
|
|
259
|
-
const events = this.state.events;
|
|
260
|
-
for (const event of events) await this.options.storage?.removeItem(EVENT + event.id);
|
|
261
|
-
this.update({
|
|
262
|
-
events: [],
|
|
263
|
-
evicted: 0
|
|
264
|
-
});
|
|
265
|
-
return {
|
|
266
|
-
cleared: true
|
|
267
|
-
};
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
async observeToken(type, value, metadata = {}) {
|
|
271
|
-
if (!this.allowed()) return;
|
|
272
|
-
if (!value || value.length > 8192) throw new Error("The notification token is empty or too long.");
|
|
273
|
-
return this.serial(async () => {
|
|
274
|
-
const token = {
|
|
275
|
-
type,
|
|
276
|
-
value,
|
|
277
|
-
observedAt: this.now(),
|
|
278
|
-
...(typeof metadata.projectId === "string" ? {
|
|
279
|
-
projectId: metadata.projectId.slice(0, 256)
|
|
280
|
-
} : {}),
|
|
281
|
-
...(typeof metadata.environment === "string" ? {
|
|
282
|
-
environment: metadata.environment.slice(0, 256)
|
|
283
|
-
} : {})
|
|
284
|
-
};
|
|
285
|
-
const tokens = [...this.state.tokens.filter(t => !(t.type === type && t.projectId === metadata.projectId && t.environment === metadata.environment)), token].slice(-10);
|
|
286
|
-
this.update({
|
|
287
|
-
tokens
|
|
288
|
-
});
|
|
289
|
-
try {
|
|
290
|
-
await this.options.storage?.setItem(TOKENS, JSON.stringify(tokens));
|
|
291
|
-
} catch (error) {
|
|
292
|
-
this.storageFailure(error);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
setPermissions(permissions) {
|
|
297
|
-
const value = (0, _json.json)(permissions);
|
|
298
|
-
this.update({
|
|
299
|
-
permissions: (0, _json.byteLength)(JSON.stringify(value)) <= MAX_RECORD_BYTES ? value : {
|
|
300
|
-
note: "Permission result exceeded the capture limit."
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
setPresented(presented) {
|
|
305
|
-
const value = (0, _json.json)(presented);
|
|
306
|
-
if ((0, _json.byteLength)(JSON.stringify(value)) <= 128 * 1024) this.update({
|
|
307
|
-
presented: value
|
|
308
|
-
});else this.update({
|
|
309
|
-
presented: {
|
|
310
|
-
note: "System tray result exceeded 128 KiB. Inspect fewer presented notifications in the app.",
|
|
311
|
-
count: Array.isArray(presented) ? presented.length : null
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
async flush() {
|
|
316
|
-
await this.queue;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
exports.NotificationRecorder = NotificationRecorder;
|
|
320
|
-
function expoMetadata(value) {
|
|
321
|
-
const raw = (0, _json.object)(value);
|
|
322
|
-
const notification = (0, _json.object)(raw.notification ?? value);
|
|
323
|
-
const request = (0, _json.object)(notification.request);
|
|
324
|
-
const content = (0, _json.object)(request.content);
|
|
325
|
-
const payload = (0, _json.object)((0, _json.object)(request.trigger).payload);
|
|
326
|
-
const data = (0, _json.object)(content.data ?? payload.body);
|
|
327
|
-
return {
|
|
328
|
-
notificationId: (0, _json.string)(request.identifier),
|
|
329
|
-
testId: (0, _json.string)(data.__buoyTestId),
|
|
330
|
-
title: (0, _json.string)(content.title),
|
|
331
|
-
actionIdentifier: (0, _json.string)(raw.actionIdentifier)
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function responseKey(value) {
|
|
335
|
-
const raw = (0, _json.object)(value);
|
|
336
|
-
const notification = (0, _json.object)(raw.notification);
|
|
337
|
-
return JSON.stringify([(0, _json.object)(notification.request).identifier, notification.date, raw.actionIdentifier, raw.userText]);
|
|
338
|
-
}
|
|
339
|
-
//# sourceMappingURL=recorder.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.NotificationRecorder=void 0;exports.expoMetadata=expoMetadata;exports.responseKey=responseKey;var _json=require("./json");const PREFIX="buoy.notifications.v1.";const SESSION=PREFIX+"session";const TOKENS=PREFIX+"tokens";const EVENT=PREFIX+"event.";const MAX_RECORD_BYTES=16*1024;const MAX_JOURNAL_BYTES=512*1024;const MAX_RECORDS=200;class NotificationRecorder{state={version:1,ready:false,provider:null,providerVersion:null,platform:"unknown",appId:null,capabilities:[],durable:false,storageError:null,evicted:0,session:null,events:[],tokens:[],permissions:null,presented:null};listeners=new Set;sequence=0;queue=Promise.resolve();constructor(t={}){this.options=t;this.now=t.now??Date.now;this.processId=t.processId??`${this.now().toString(36)}-${Math.random().toString(36).slice(2)}`;this.state.durable=!!t.storage&&t.durable===true;this.ready=this.hydrate();this.queue=this.ready}getSnapshot=()=>this.state;subscribe=t=>{this.listeners.add(t);return()=>{this.listeners.delete(t)}};update(t){this.state={...this.state,...t};for(const e of this.listeners){try{e()}catch{}}}configure(t){this.update(t)}allowed(){return this.options.enabled?.()??true}storageFailure(t){this.update({storageError:t instanceof Error?t.message:String(t),durable:false})}serial(t){const e=this.queue.then(t);this.queue=e.catch(()=>{});return e}async hydrate(){if(!this.allowed()){this.update({ready:true});return}if(this.options.durability){try{this.update({durable:await this.options.durability})}catch(s){this.storageFailure(s)}}const t=this.options.storage;let e=false;if(t)try{const s=await t.getItem(SESSION);e=s==="null";if(s){const i=JSON.parse(s);if(i&&typeof i.runId==="string"&&Number.isFinite(i.expiresAt)&&i.expiresAt>this.now())this.update({session:i})}const r=await t.getItem(TOKENS);if(r){const i=JSON.parse(r);if(Array.isArray(i))this.update({tokens:i.filter(o=>o&&["apns","fcm","expo"].includes(o.type)&&typeof o.value==="string").slice(-10)})}const n=(await t.getAllKeys()).filter(i=>i.startsWith(EVENT)).sort();const a=[];for(const i of n){try{const o=await t.getItem(i);if(!o||(0,_json.byteLength)(o)>MAX_RECORD_BYTES){await t.removeItem(i);continue}const c=JSON.parse(o);if(typeof c.id==="string"&&typeof c.kind==="string"&&Number.isFinite(c.capturedAt))a.push(c);else await t.removeItem(i)}catch{await t.removeItem(i)}}a.sort((i,o)=>i.capturedAt-o.capturedAt||i.sequence-o.sequence||i.id.localeCompare(o.id));this.update({events:a});await this.prune()}catch(s){this.storageFailure(s)}if(this.options.autoStart&&!this.state.session&&!e){const s=this.now();const r={runId:this.processId,startedAt:s,expiresAt:s+30*60*1e3};try{await t?.setItem(SESSION,JSON.stringify(r))}catch(n){this.storageFailure(n)}this.update({session:r})}this.update({ready:true})}async setCaptureSession(t,e=30*60*1e3){if(!this.allowed())throw new Error("Notification capture is disabled in this build.");if(t!==null&&(typeof t!=="string"||!t.trim()||t.length>128))throw new Error("runId must contain 1 to 128 characters, or be null to stop capture.");if(!Number.isFinite(e)||e<1e3||e>60*60*1e3)throw new Error("Capture duration must be between 1 second and 1 hour.");return this.serial(async()=>{const s=this.now();const r=t===null?null:{runId:t,startedAt:s,expiresAt:s+e};try{if(this.options.storage)await this.options.storage.setItem(SESSION,JSON.stringify(r))}catch(n){this.storageFailure(n);throw new Error("Could not persist the capture session. Keep the app open and check storage.")}this.update({session:r});return{session:r,durable:this.state.durable}})}record(t,e,s={}){const r=this.now();const n=this.options.appState?.()??"unknown";const a=(0,_json.json)(e);return this.serial(async()=>{if(!this.allowed())return null;const i=this.state.session;if(!i||i.expiresAt<=r){if(i)this.update({session:null});return null}const o=(u,l=512)=>typeof u==="string"?u.slice(0,l):null;const c=o(s.dedupeKey,2048);if(c&&this.state.events.some(u=>u.dedupeKey===c))return null;const d={id:`${this.processId}-${++this.sequence}`,processId:this.processId,sequence:this.sequence,capturedAt:r,runId:i.runId,kind:t,provider:o(s.provider,64)??this.state.provider??"app",observedAppState:n,notificationId:o(s.notificationId),testId:o(s.testId),title:o(s.title,1024),actionIdentifier:o(s.actionIdentifier),...c?{dedupeKey:c}:{},raw:a};if((0,_json.byteLength)(JSON.stringify(d))>MAX_RECORD_BYTES){d.raw={note:"Payload exceeded the 16 KiB capture limit."};d.truncated=true}this.update({events:[...this.state.events,d]});try{await this.options.storage?.setItem(EVENT+d.id,JSON.stringify(d))}catch(u){this.storageFailure(u)}try{await this.prune()}catch(u){this.storageFailure(u)}return d})}async prune(){const t=[...this.state.events];const e=t.map(n=>(0,_json.byteLength)(JSON.stringify(n)));let s=e.reduce((n,a)=>n+a,0);const r=[];while(t.length>MAX_RECORDS||s>MAX_JOURNAL_BYTES){r.push(t.shift());s-=e.shift()}if(r.length)this.update({events:t,evicted:this.state.evicted+r.length});for(const n of r)await this.options.storage?.removeItem(EVENT+n.id)}async clearCapturedEvents(){return this.serial(async()=>{const t=this.state.events;for(const e of t)await this.options.storage?.removeItem(EVENT+e.id);this.update({events:[],evicted:0});return{cleared:true}})}async observeToken(t,e,s={}){if(!this.allowed())return;if(!e||e.length>8192)throw new Error("The notification token is empty or too long.");return this.serial(async()=>{const r={type:t,value:e,observedAt:this.now(),...typeof s.projectId==="string"?{projectId:s.projectId.slice(0,256)}:{},...typeof s.environment==="string"?{environment:s.environment.slice(0,256)}:{}};const n=[...this.state.tokens.filter(a=>!(a.type===t&&a.projectId===s.projectId&&a.environment===s.environment)),r].slice(-10);this.update({tokens:n});try{await this.options.storage?.setItem(TOKENS,JSON.stringify(n))}catch(a){this.storageFailure(a)}})}setPermissions(t){const e=(0,_json.json)(t);this.update({permissions:(0,_json.byteLength)(JSON.stringify(e))<=MAX_RECORD_BYTES?e:{note:"Permission result exceeded the capture limit."}})}setPresented(t){const e=(0,_json.json)(t);if((0,_json.byteLength)(JSON.stringify(e))<=128*1024)this.update({presented:e});else this.update({presented:{note:"System tray result exceeded 128 KiB. Inspect fewer presented notifications in the app.",count:Array.isArray(t)?t.length:null}})}async flush(){await this.queue}}exports.NotificationRecorder=NotificationRecorder;function expoMetadata(h){const t=(0,_json.object)(h);const e=(0,_json.object)(t.notification??h);const s=(0,_json.object)(e.request);const r=(0,_json.object)(s.content);const n=(0,_json.object)((0,_json.object)(s.trigger).payload);const a=(0,_json.object)(r.data??n.body);return{notificationId:(0,_json.string)(s.identifier),testId:(0,_json.string)(a.__buoyTestId),title:(0,_json.string)(r.title),actionIdentifier:(0,_json.string)(t.actionIdentifier)}}function responseKey(h){const t=(0,_json.object)(h);const e=(0,_json.object)(t.notification);return JSON.stringify([(0,_json.object)(e.request).identifier,e.date,t.actionIdentifier,t.userText])}
|
package/lib/commonjs/runtime.js
CHANGED
|
@@ -1,110 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.captureEnabled = captureEnabled;
|
|
7
|
-
exports.getNotificationRecorder = getNotificationRecorder;
|
|
8
|
-
exports.installExpoNotificationCapture = installExpoNotificationCapture;
|
|
9
|
-
exports.notificationSyncAdapter = void 0;
|
|
10
|
-
var _reactNative = require("react-native");
|
|
11
|
-
var _storage = require("./storage");
|
|
12
|
-
var _recorder = require("./recorder");
|
|
13
|
-
var _expo = require("./expo");
|
|
14
|
-
var _json = require("./json");
|
|
15
|
-
const globals = globalThis;
|
|
16
|
-
function captureEnabled(enableInRelease = false) {
|
|
17
|
-
return typeof __DEV__ !== "undefined" && __DEV__ || enableInRelease === true;
|
|
18
|
-
}
|
|
19
|
-
function getNotificationRecorder() {
|
|
20
|
-
return globals.__buoyNotificationsV1?.recorder ?? null;
|
|
21
|
-
}
|
|
22
|
-
function installExpoNotificationCapture(sdk, options = {}) {
|
|
23
|
-
if (!captureEnabled(options.enableInRelease)) return null;
|
|
24
|
-
const key = JSON.stringify([options.platform ?? _reactNative.Platform.OS, options.appId, options.providerVersion, options.projectId, options.enableInRelease, options.preview]);
|
|
25
|
-
let runtime = globals.__buoyNotificationsV1;
|
|
26
|
-
if (!runtime) {
|
|
27
|
-
const persistence = options.storage ? {
|
|
28
|
-
storage: options.storage,
|
|
29
|
-
durable: options.durable === true
|
|
30
|
-
} : (0, _storage.defaultNotificationStorage)();
|
|
31
|
-
const recorder = new _recorder.NotificationRecorder({
|
|
32
|
-
...persistence,
|
|
33
|
-
enabled: () => captureEnabled(options.enableInRelease),
|
|
34
|
-
appState: () => _reactNative.AppState.currentState ?? "unknown",
|
|
35
|
-
autoStart: true
|
|
36
|
-
});
|
|
37
|
-
runtime = {
|
|
38
|
-
recorder
|
|
39
|
-
};
|
|
40
|
-
globals.__buoyNotificationsV1 = runtime;
|
|
41
|
-
}
|
|
42
|
-
if (runtime.sdk !== sdk || runtime.key !== key) {
|
|
43
|
-
runtime.attachment?.dispose();
|
|
44
|
-
runtime.attachment = (0, _expo.attachExpoNotifications)(runtime.recorder, sdk, {
|
|
45
|
-
...options,
|
|
46
|
-
platform: options.platform ?? _reactNative.Platform.OS
|
|
47
|
-
});
|
|
48
|
-
runtime.sdk = sdk;
|
|
49
|
-
runtime.key = key;
|
|
50
|
-
}
|
|
51
|
-
return runtime.recorder;
|
|
52
|
-
}
|
|
53
|
-
const emptyRecorder = new _recorder.NotificationRecorder();
|
|
54
|
-
function current() {
|
|
55
|
-
const runtime = globals.__buoyNotificationsV1;
|
|
56
|
-
if (!runtime) throw new Error("Install Expo notification capture in the app entry point, before rendering the root component.");
|
|
57
|
-
return runtime;
|
|
58
|
-
}
|
|
59
|
-
const notificationSyncAdapter = exports.notificationSyncAdapter = {
|
|
60
|
-
version: 1,
|
|
61
|
-
getSnapshot: () => (getNotificationRecorder() ?? emptyRecorder).getSnapshot(),
|
|
62
|
-
subscribe: listener => (getNotificationRecorder() ?? emptyRecorder).subscribe(listener),
|
|
63
|
-
actions: {
|
|
64
|
-
getSnapshot: () => notificationSyncAdapter.getSnapshot(),
|
|
65
|
-
getCapabilities: () => {
|
|
66
|
-
const {
|
|
67
|
-
ready,
|
|
68
|
-
provider,
|
|
69
|
-
providerVersion,
|
|
70
|
-
platform,
|
|
71
|
-
appId,
|
|
72
|
-
capabilities,
|
|
73
|
-
durable,
|
|
74
|
-
storageError
|
|
75
|
-
} = notificationSyncAdapter.getSnapshot();
|
|
76
|
-
return {
|
|
77
|
-
ready,
|
|
78
|
-
provider,
|
|
79
|
-
providerVersion,
|
|
80
|
-
platform,
|
|
81
|
-
appId,
|
|
82
|
-
capabilities,
|
|
83
|
-
durable,
|
|
84
|
-
storageError
|
|
85
|
-
};
|
|
86
|
-
},
|
|
87
|
-
getEvent(params) {
|
|
88
|
-
const {
|
|
89
|
-
id
|
|
90
|
-
} = (0, _json.object)(params);
|
|
91
|
-
if (typeof id !== "string") throw new Error("Provide the captured event id.");
|
|
92
|
-
return current().recorder.getSnapshot().events.find(event => event.id === id) ?? null;
|
|
93
|
-
},
|
|
94
|
-
async setCaptureSession(params) {
|
|
95
|
-
const {
|
|
96
|
-
runId,
|
|
97
|
-
durationMs
|
|
98
|
-
} = (0, _json.object)(params);
|
|
99
|
-
if (typeof runId !== "string" && runId !== null) throw new Error("Provide runId, or null to stop capture.");
|
|
100
|
-
if (durationMs !== undefined && typeof durationMs !== "number") throw new Error("durationMs must be a number.");
|
|
101
|
-
return current().recorder.setCaptureSession(runId, durationMs);
|
|
102
|
-
},
|
|
103
|
-
clearCapturedEvents: () => current().recorder.clearCapturedEvents(),
|
|
104
|
-
getPermissions: () => current().attachment.actions.getPermissions(),
|
|
105
|
-
listPresented: () => current().attachment.actions.listPresented(),
|
|
106
|
-
refreshToken: params => current().attachment.actions.refreshToken(params),
|
|
107
|
-
scheduleLocal: params => current().attachment.actions.scheduleLocal(params)
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
//# sourceMappingURL=runtime.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.captureEnabled=captureEnabled;exports.getNotificationRecorder=getNotificationRecorder;exports.installExpoNotificationCapture=installExpoNotificationCapture;exports.notificationSyncAdapter=void 0;var _reactNative=require("react-native");var _storage=require("./storage");var _recorder=require("./recorder");var _expo=require("./expo");var _json=require("./json");const globals=globalThis;function captureEnabled(t=false){return typeof __DEV__!=="undefined"&&__DEV__||t===true}function getNotificationRecorder(){return globals.__buoyNotificationsV1?.recorder??null}function installExpoNotificationCapture(t,e={}){if(!captureEnabled(e.enableInRelease))return null;const o=JSON.stringify([e.platform??_reactNative.Platform.OS,e.appId,e.providerVersion,e.projectId,e.enableInRelease,e.preview]);let r=globals.__buoyNotificationsV1;if(!r){const n=e.storage?{storage:e.storage,durable:e.durable===true}:(0,_storage.defaultNotificationStorage)();const a=new _recorder.NotificationRecorder({...n,enabled:()=>captureEnabled(e.enableInRelease),appState:()=>_reactNative.AppState.currentState??"unknown",autoStart:true});r={recorder:a};globals.__buoyNotificationsV1=r}if(r.sdk!==t||r.key!==o){r.attachment?.dispose();r.attachment=(0,_expo.attachExpoNotifications)(r.recorder,t,{...e,platform:e.platform??_reactNative.Platform.OS});r.sdk=t;r.key=o}return r.recorder}const emptyRecorder=new _recorder.NotificationRecorder;function current(){const t=globals.__buoyNotificationsV1;if(!t)throw new Error("Install Expo notification capture in the app entry point, before rendering the root component.");return t}const notificationSyncAdapter=exports.notificationSyncAdapter={version:1,getSnapshot:()=>(getNotificationRecorder()??emptyRecorder).getSnapshot(),subscribe:t=>(getNotificationRecorder()??emptyRecorder).subscribe(t),actions:{getSnapshot:()=>notificationSyncAdapter.getSnapshot(),getCapabilities:()=>{const{ready:t,provider:e,providerVersion:o,platform:r,appId:n,capabilities:a,durable:i,storageError:c}=notificationSyncAdapter.getSnapshot();return{ready:t,provider:e,providerVersion:o,platform:r,appId:n,capabilities:a,durable:i,storageError:c}},getEvent(t){const{id:e}=(0,_json.object)(t);if(typeof e!=="string")throw new Error("Provide the captured event id.");return current().recorder.getSnapshot().events.find(o=>o.id===e)??null},async setCaptureSession(t){const{runId:e,durationMs:o}=(0,_json.object)(t);if(typeof e!=="string"&&e!==null)throw new Error("Provide runId, or null to stop capture.");if(o!==void 0&&typeof o!=="number")throw new Error("durationMs must be a number.");return current().recorder.setCaptureSession(e,o)},clearCapturedEvents:()=>current().recorder.clearCapturedEvents(),getPermissions:()=>current().attachment.actions.getPermissions(),listPresented:()=>current().attachment.actions.listPresented(),refreshToken:t=>current().attachment.actions.refreshToken(t),scheduleLocal:t=>current().attachment.actions.scheduleLocal(t)}};
|
package/lib/commonjs/storage.js
CHANGED
|
@@ -1,61 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.createExpoNotificationStorage = createExpoNotificationStorage;
|
|
7
|
-
exports.defaultNotificationStorage = defaultNotificationStorage;
|
|
8
|
-
/** Propagates native I/O failures so capture never acknowledges a memory fallback as durable. */
|
|
9
|
-
function createExpoNotificationStorage(fs) {
|
|
10
|
-
if (!fs.documentDirectory) throw new Error("Expo FileSystem has no persistent document directory.");
|
|
11
|
-
const directory = fs.documentDirectory + "buoy-notifications/";
|
|
12
|
-
const pathFor = key => directory + encodeURIComponent(key) + ".json";
|
|
13
|
-
return {
|
|
14
|
-
async getItem(key) {
|
|
15
|
-
const path = pathFor(key);
|
|
16
|
-
return (await fs.getInfoAsync(path)).exists ? fs.readAsStringAsync(path) : null;
|
|
17
|
-
},
|
|
18
|
-
async setItem(key, value) {
|
|
19
|
-
if (!(await fs.getInfoAsync(directory)).exists) await fs.makeDirectoryAsync(directory, {
|
|
20
|
-
intermediates: true
|
|
21
|
-
});
|
|
22
|
-
await fs.writeAsStringAsync(pathFor(key), value);
|
|
23
|
-
},
|
|
24
|
-
async removeItem(key) {
|
|
25
|
-
await fs.deleteAsync(pathFor(key), {
|
|
26
|
-
idempotent: true
|
|
27
|
-
});
|
|
28
|
-
},
|
|
29
|
-
async getAllKeys() {
|
|
30
|
-
if (!(await fs.getInfoAsync(directory)).exists) return [];
|
|
31
|
-
return (await fs.readDirectoryAsync(directory)).filter(file => file.endsWith(".json")).flatMap(file => {
|
|
32
|
-
try {
|
|
33
|
-
return [decodeURIComponent(file.slice(0, -5))];
|
|
34
|
-
} catch {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function defaultNotificationStorage() {
|
|
42
|
-
try {
|
|
43
|
-
const fs = require("expo-file-system/legacy");
|
|
44
|
-
if (fs.documentDirectory && typeof fs.writeAsStringAsync === "function") return {
|
|
45
|
-
storage: createExpoNotificationStorage(fs),
|
|
46
|
-
durable: true
|
|
47
|
-
};
|
|
48
|
-
} catch {/* An app can provide its own storage when Expo FileSystem is absent. */}
|
|
49
|
-
try {
|
|
50
|
-
const module = require("@react-native-async-storage/async-storage");
|
|
51
|
-
const storage = module.default ?? module;
|
|
52
|
-
if (typeof storage.getItem === "function" && typeof storage.setItem === "function" && typeof storage.getAllKeys === "function") return {
|
|
53
|
-
storage,
|
|
54
|
-
durable: true
|
|
55
|
-
};
|
|
56
|
-
} catch {/* Capture remains available in memory. */}
|
|
57
|
-
return {
|
|
58
|
-
durable: false
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
//# sourceMappingURL=storage.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.createExpoNotificationStorage=createExpoNotificationStorage;exports.defaultNotificationStorage=defaultNotificationStorage;function createExpoNotificationStorage(t){if(!t.documentDirectory)throw new Error("Expo FileSystem has no persistent document directory.");const r=t.documentDirectory+"buoy-notifications/";const o=e=>r+encodeURIComponent(e)+".json";return{async getItem(e){const n=o(e);return(await t.getInfoAsync(n)).exists?t.readAsStringAsync(n):null},async setItem(e,n){if(!(await t.getInfoAsync(r)).exists)await t.makeDirectoryAsync(r,{intermediates:true});await t.writeAsStringAsync(o(e),n)},async removeItem(e){await t.deleteAsync(o(e),{idempotent:true})},async getAllKeys(){if(!(await t.getInfoAsync(r)).exists)return[];return(await t.readDirectoryAsync(r)).filter(e=>e.endsWith(".json")).flatMap(e=>{try{return[decodeURIComponent(e.slice(0,-5))]}catch{return[]}})}}}function defaultNotificationStorage(){try{const t=require("expo-file-system/legacy");if(t.documentDirectory&&typeof t.writeAsStringAsync==="function")return{storage:createExpoNotificationStorage(t),durable:true}}catch{}try{const t=require("@react-native-async-storage/async-storage");const r=t.default??t;if(typeof r.getItem==="function"&&typeof r.setItem==="function"&&typeof r.getAllKeys==="function")return{storage:r,durable:true}}catch{}return{durable:false}}
|
package/lib/commonjs/types.js
CHANGED