noibu-react-native 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +69 -0
  3. package/dist/api/ClientConfig.d.ts +3 -0
  4. package/dist/api/ClientConfig.js +3 -0
  5. package/dist/api/MetroplexSocket.d.ts +1 -1
  6. package/dist/api/MetroplexSocket.js +11 -5
  7. package/dist/constants.js +1 -1
  8. package/dist/mobileTransformer/mobile-replay/index.d.ts +10 -0
  9. package/dist/mobileTransformer/mobile-replay/index.js +57 -0
  10. package/dist/mobileTransformer/mobile-replay/mobile.types.d.ts +312 -0
  11. package/dist/mobileTransformer/mobile-replay/mobile.types.js +11 -0
  12. package/dist/mobileTransformer/mobile-replay/rrweb.d.ts +573 -0
  13. package/dist/mobileTransformer/mobile-replay/rrweb.js +39 -0
  14. package/dist/mobileTransformer/mobile-replay/schema/mobile/rr-mobile-schema.json.js +1559 -0
  15. package/dist/mobileTransformer/mobile-replay/schema/web/rr-web-schema.json.js +1183 -0
  16. package/dist/mobileTransformer/mobile-replay/transformer/colors.d.ts +1 -0
  17. package/dist/mobileTransformer/mobile-replay/transformer/colors.js +43 -0
  18. package/dist/mobileTransformer/mobile-replay/transformer/screen-chrome.d.ts +13 -0
  19. package/dist/mobileTransformer/mobile-replay/transformer/screen-chrome.js +142 -0
  20. package/dist/mobileTransformer/mobile-replay/transformer/transformers.d.ts +59 -0
  21. package/dist/mobileTransformer/mobile-replay/transformer/transformers.js +1160 -0
  22. package/dist/mobileTransformer/mobile-replay/transformer/types.d.ts +40 -0
  23. package/dist/mobileTransformer/mobile-replay/transformer/wireframeStyle.d.ts +16 -0
  24. package/dist/mobileTransformer/mobile-replay/transformer/wireframeStyle.js +197 -0
  25. package/dist/mobileTransformer/utils.d.ts +1 -0
  26. package/dist/mobileTransformer/utils.js +5 -0
  27. package/dist/monitors/http-tools/GqlErrorValidator.js +4 -3
  28. package/dist/sessionRecorder/SessionRecorder.js +7 -4
  29. package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +23 -3
  30. package/dist/sessionRecorder/types.d.ts +1 -1
  31. package/dist/utils/piiRedactor.js +15 -2
  32. package/dist/utils/piiRedactor.test.d.ts +1 -0
  33. package/ios/IOSPocEmitter.h +7 -0
  34. package/ios/IOSPocEmitter.m +33 -0
  35. package/noibu-react-native.podspec +50 -0
  36. package/package.json +17 -4
  37. package/android/.gitignore +0 -13
@@ -0,0 +1,1160 @@
1
+ import { IncrementalSource, EventType } from '../rrweb.js';
2
+ import { isObject } from '../../utils.js';
3
+ import { NodeType } from '../mobile.types.js';
4
+ import { makeOpenKeyboardPlaceholder, makeStatusBar, makeNavigationBar } from './screen-chrome.js';
5
+ import { makeHTMLStyles, makeBodyStyles, makeStylesString, asStyleString, makeMinimalStyles, makeDeterminateProgressStyles, makeIndeterminateProgressStyles, makePositionStyles, makeColorStyles } from './wireframeStyle.js';
6
+ import { noibuLog } from '../../../utils/log.js';
7
+
8
+ const BACKGROUND = '#f3f4ef';
9
+ const FOREGROUND = '#35373e';
10
+ /**
11
+ * generates a sequence of ids
12
+ * from 100 to 9,999,999
13
+ * the transformer reserves ids in the range 0 to 9,999,999
14
+ * we reserve a range of ids because we need nodes to have stable ids across snapshots
15
+ * in order for incremental snapshots to work
16
+ * some mobile elements have to be wrapped in other elements in order to be styled correctly
17
+ * which means the web version of a mobile replay will use ids that don't exist in the mobile replay,
18
+ * and we need to ensure they don't clash
19
+ * -----
20
+ * id is typed as a number in rrweb
21
+ * and there's a few places in their code where rrweb uses a check for `id === -1` to bail out of processing
22
+ * so, it's safest to assume that id is expected to be a positive integer
23
+ */
24
+ function* ids() {
25
+ let i = 100;
26
+ while (i < 9999999) {
27
+ yield i++;
28
+ }
29
+ }
30
+ let globalIdSequence = ids();
31
+ // there are some fixed ids that we need to use for fixed elements or artificial mutations
32
+ const DOCUMENT_ID = 1;
33
+ const HTML_DOC_TYPE_ID = 2;
34
+ const HTML_ELEMENT_ID = 3;
35
+ const HEAD_ID = 4;
36
+ const BODY_ID = 5;
37
+ // the nav bar should always be the last item in the body so that it is at the top of the stack
38
+ const NAVIGATION_BAR_PARENT_ID = 7;
39
+ const NAVIGATION_BAR_ID = 8;
40
+ // the keyboard so that it is still before the nav bar
41
+ const KEYBOARD_PARENT_ID = 9;
42
+ const KEYBOARD_ID = 10;
43
+ const STATUS_BAR_PARENT_ID = 11;
44
+ const STATUS_BAR_ID = 12;
45
+ function isKeyboardEvent(x) {
46
+ return (isObject(x) &&
47
+ 'data' in x &&
48
+ isObject(x.data) &&
49
+ 'tag' in x.data &&
50
+ x.data.tag === 'keyboard');
51
+ }
52
+ function _isPositiveInteger(id) {
53
+ return typeof id === 'number' && id > 0 && id % 1 === 0;
54
+ }
55
+ function _isNullish(x) {
56
+ return x === null || x === undefined;
57
+ }
58
+ function isRemovedNodeMutation(x) {
59
+ return isObject(x) && 'id' in x;
60
+ }
61
+ const makeCustomEvent = (mobileCustomEvent) => {
62
+ if (isKeyboardEvent(mobileCustomEvent)) {
63
+ // keyboard events are handled as incremental snapshots to add or remove a keyboard from the DOM
64
+ // TODO eventually we can pass something to makeIncrementalEvent here
65
+ const adds = [];
66
+ const removes = [];
67
+ if (mobileCustomEvent.data.payload.open) {
68
+ const keyboardPlaceHolder = makeOpenKeyboardPlaceholder(mobileCustomEvent, {
69
+ timestamp: mobileCustomEvent.timestamp,
70
+ idSequence: globalIdSequence,
71
+ });
72
+ if (keyboardPlaceHolder) {
73
+ adds.push({
74
+ parentId: KEYBOARD_PARENT_ID,
75
+ nextId: null,
76
+ node: keyboardPlaceHolder.result,
77
+ });
78
+ // mutations seem not to want a tree of nodes to add
79
+ // so even though `keyboardPlaceholder` is a tree with content
80
+ // we have to add the text content as well
81
+ adds.push({
82
+ parentId: keyboardPlaceHolder.result.id,
83
+ nextId: null,
84
+ node: {
85
+ type: NodeType.Text,
86
+ id: globalIdSequence.next().value,
87
+ textContent: 'keyboard',
88
+ },
89
+ });
90
+ }
91
+ else {
92
+ //captureMessage('Failed to create keyboard placeholder', { extra: { mobileCustomEvent } })
93
+ console.error('Failed to create keyboard placeholder', mobileCustomEvent);
94
+ }
95
+ }
96
+ else {
97
+ removes.push(
98
+ //@ts-ignore
99
+ {
100
+ parentId: KEYBOARD_PARENT_ID,
101
+ id: KEYBOARD_ID,
102
+ });
103
+ }
104
+ const mutation = {
105
+ adds,
106
+ attributes: [],
107
+ removes,
108
+ source: IncrementalSource.Mutation,
109
+ texts: [],
110
+ };
111
+ return {
112
+ type: EventType.IncrementalSnapshot,
113
+ data: mutation,
114
+ timestamp: mobileCustomEvent.timestamp,
115
+ };
116
+ }
117
+ return mobileCustomEvent;
118
+ };
119
+ const makeMetaEvent = (mobileMetaEvent) => ({
120
+ type: EventType.Meta,
121
+ data: {
122
+ href: mobileMetaEvent.data.href || '', // the replay doesn't use the href, so we safely ignore any absence
123
+ // mostly we need width and height in order to size the viewport
124
+ width: mobileMetaEvent.data.width,
125
+ height: mobileMetaEvent.data.height,
126
+ },
127
+ timestamp: mobileMetaEvent.timestamp,
128
+ });
129
+ function makeDivElement(wireframe, children, context) {
130
+ const _id = _isPositiveInteger(wireframe.id)
131
+ ? wireframe.id
132
+ : context.idSequence.next().value;
133
+ return {
134
+ result: {
135
+ type: NodeType.Element,
136
+ tagName: 'div',
137
+ attributes: {
138
+ style: asStyleString([
139
+ makeStylesString(wireframe),
140
+ 'overflow:hidden',
141
+ 'white-space:nowrap',
142
+ ]),
143
+ 'data-rrweb-id': _id,
144
+ },
145
+ id: _id,
146
+ childNodes: children,
147
+ },
148
+ context,
149
+ };
150
+ }
151
+ function makeTextElement(wireframe, children, context) {
152
+ if (wireframe.type !== 'text') {
153
+ console.error('Passed incorrect wireframe type to makeTextElement');
154
+ return null;
155
+ }
156
+ // because we might have to style the text, we always wrap it in a div
157
+ // and apply styles to that
158
+ const id = context.idSequence.next().value;
159
+ const childNodes = [...children];
160
+ if (!_isNullish(wireframe.text)) {
161
+ childNodes.unshift({
162
+ type: NodeType.Text,
163
+ textContent: wireframe.text,
164
+ // since the text node is wrapped, we assign it a synthetic id
165
+ id,
166
+ });
167
+ }
168
+ return {
169
+ result: {
170
+ type: NodeType.Element,
171
+ tagName: 'div',
172
+ attributes: {
173
+ style: asStyleString([
174
+ makeStylesString(wireframe),
175
+ 'overflow:hidden',
176
+ 'white-space:normal',
177
+ ]),
178
+ 'data-rrweb-id': wireframe.id,
179
+ },
180
+ id: wireframe.id,
181
+ childNodes,
182
+ },
183
+ context,
184
+ };
185
+ }
186
+ function makeWebViewElement(wireframe, children, context) {
187
+ const labelledWireframe = Object.assign({}, wireframe);
188
+ if ('url' in wireframe) {
189
+ labelledWireframe.label = wireframe.url;
190
+ }
191
+ return makePlaceholderElement(labelledWireframe, children, context);
192
+ }
193
+ function makePlaceholderElement(wireframe, children, context) {
194
+ var _a, _b;
195
+ const txt = 'label' in wireframe && wireframe.label
196
+ ? wireframe.label
197
+ : wireframe.type || 'PLACEHOLDER';
198
+ return {
199
+ result: {
200
+ type: NodeType.Element,
201
+ tagName: 'div',
202
+ attributes: {
203
+ style: makeStylesString(wireframe, Object.assign({ verticalAlign: 'center', horizontalAlign: 'center', backgroundColor: ((_a = wireframe.style) === null || _a === void 0 ? void 0 : _a.backgroundColor) || BACKGROUND, color: ((_b = wireframe.style) === null || _b === void 0 ? void 0 : _b.color) || FOREGROUND,
204
+ //backgroundImage: PLACEHOLDER_SVG_DATA_IMAGE_URL,
205
+ backgroundSize: 'auto', backgroundRepeat: 'unset' }, context.styleOverride)),
206
+ 'data-rrweb-id': wireframe.id,
207
+ },
208
+ id: wireframe.id,
209
+ childNodes: [
210
+ {
211
+ type: NodeType.Text,
212
+ // since the text node is wrapped, we assign it a synthetic id
213
+ id: context.idSequence.next().value,
214
+ textContent: txt,
215
+ },
216
+ ...children,
217
+ ],
218
+ },
219
+ context,
220
+ };
221
+ }
222
+ function dataURIOrPNG(src) {
223
+ // replace all new lines in src
224
+ src = src.replace(/\r?\n|\r/g, '');
225
+ // if (!src.startsWith('data:image/')) {
226
+ // return 'data:image/png;base64,' + src;
227
+ // }
228
+ noibuLog("this is the url: ", src);
229
+ return src;
230
+ }
231
+ function makeImageElement(wireframe, children, context) {
232
+ if (!wireframe.base64) {
233
+ return makePlaceholderElement(wireframe, children, context);
234
+ }
235
+ const src = dataURIOrPNG(wireframe.base64);
236
+ return {
237
+ result: {
238
+ type: NodeType.Element,
239
+ tagName: 'img',
240
+ attributes: {
241
+ src: src,
242
+ width: wireframe.width,
243
+ height: wireframe.height,
244
+ style: makeStylesString(wireframe),
245
+ 'data-rrweb-id': wireframe.id,
246
+ },
247
+ id: wireframe.id,
248
+ childNodes: children,
249
+ },
250
+ context,
251
+ };
252
+ }
253
+ function inputAttributes(wireframe) {
254
+ const attributes = Object.assign(Object.assign({ style: makeStylesString(wireframe), type: wireframe.inputType }, (wireframe.disabled ? { disabled: wireframe.disabled } : {})), { 'data-rrweb-id': wireframe.id });
255
+ switch (wireframe.inputType) {
256
+ case 'checkbox':
257
+ return Object.assign(Object.assign(Object.assign({}, attributes), { style: null }), (wireframe.checked ? { checked: wireframe.checked } : {}));
258
+ case 'toggle':
259
+ return Object.assign(Object.assign(Object.assign({}, attributes), { style: null }), (wireframe.checked ? { checked: wireframe.checked } : {}));
260
+ case 'radio':
261
+ return Object.assign(Object.assign(Object.assign({}, attributes), { style: null }), (wireframe.checked ? { checked: wireframe.checked } : {}));
262
+ case 'button':
263
+ return Object.assign({}, attributes);
264
+ case 'text_area':
265
+ return Object.assign(Object.assign({}, attributes), { value: wireframe.value || '' });
266
+ case 'progress':
267
+ return Object.assign(Object.assign({}, attributes), {
268
+ // indeterminate when omitted
269
+ value: wireframe.value || null,
270
+ // defaults to 1 when omitted
271
+ max: wireframe.max || null, type: null });
272
+ default:
273
+ return Object.assign(Object.assign({}, attributes), { value: wireframe.value || '' });
274
+ }
275
+ }
276
+ function makeButtonElement(wireframe, children, context) {
277
+ const buttonText = wireframe.value
278
+ ? {
279
+ type: NodeType.Text,
280
+ textContent: wireframe.value,
281
+ }
282
+ : null;
283
+ return {
284
+ result: {
285
+ type: NodeType.Element,
286
+ tagName: 'button',
287
+ attributes: inputAttributes(wireframe),
288
+ id: wireframe.id,
289
+ childNodes: buttonText
290
+ ? [
291
+ Object.assign(Object.assign({}, buttonText), { id: context.idSequence.next().value }),
292
+ ...children,
293
+ ]
294
+ : children,
295
+ },
296
+ context,
297
+ };
298
+ }
299
+ function makeSelectOptionElement(option, selected, context) {
300
+ const optionId = context.idSequence.next().value;
301
+ return {
302
+ result: {
303
+ type: NodeType.Element,
304
+ tagName: 'option',
305
+ attributes: Object.assign(Object.assign({}, (selected ? { selected: selected } : {})), { 'data-rrweb-id': optionId }),
306
+ id: optionId,
307
+ childNodes: [
308
+ {
309
+ type: NodeType.Text,
310
+ textContent: option,
311
+ id: context.idSequence.next().value,
312
+ },
313
+ ],
314
+ },
315
+ context,
316
+ };
317
+ }
318
+ function makeSelectElement(wireframe, children, context) {
319
+ const selectOptions = [];
320
+ if (wireframe.options) {
321
+ let optionContext = context;
322
+ for (let i = 0; i < wireframe.options.length; i++) {
323
+ const option = wireframe.options[i];
324
+ const conversion = makeSelectOptionElement(option, wireframe.value === option, optionContext);
325
+ selectOptions.push(conversion.result);
326
+ optionContext = conversion.context;
327
+ }
328
+ }
329
+ return {
330
+ result: {
331
+ type: NodeType.Element,
332
+ tagName: 'select',
333
+ attributes: inputAttributes(wireframe),
334
+ id: wireframe.id,
335
+ childNodes: [...selectOptions, ...children],
336
+ },
337
+ context,
338
+ };
339
+ }
340
+ function groupRadioButtons(children, radioGroupName) {
341
+ return children.map(child => {
342
+ if (child.type === NodeType.Element &&
343
+ child.tagName === 'input' &&
344
+ child.attributes.type === 'radio') {
345
+ return Object.assign(Object.assign({}, child), { attributes: Object.assign(Object.assign({}, child.attributes), { name: radioGroupName, 'data-rrweb-id': child.id }) });
346
+ }
347
+ return child;
348
+ });
349
+ }
350
+ function makeRadioGroupElement(wireframe, children, context) {
351
+ const radioGroupName = 'radio_group_' + wireframe.id;
352
+ return {
353
+ result: {
354
+ type: NodeType.Element,
355
+ tagName: 'div',
356
+ attributes: {
357
+ style: makeStylesString(wireframe),
358
+ 'data-rrweb-id': wireframe.id,
359
+ },
360
+ id: wireframe.id,
361
+ childNodes: groupRadioButtons(children, radioGroupName),
362
+ },
363
+ context,
364
+ };
365
+ }
366
+ function makeStar(title, path, context) {
367
+ const svgId = context.idSequence.next().value;
368
+ const titleId = context.idSequence.next().value;
369
+ const pathId = context.idSequence.next().value;
370
+ return {
371
+ type: NodeType.Element,
372
+ tagName: 'svg',
373
+ isSVG: true,
374
+ attributes: {
375
+ style: asStyleString([
376
+ 'height: 100%',
377
+ 'overflow-clip-margin: content-box',
378
+ 'overflow:hidden',
379
+ ]),
380
+ viewBox: '0 0 24 24',
381
+ fill: 'currentColor',
382
+ 'data-rrweb-id': svgId,
383
+ },
384
+ id: svgId,
385
+ childNodes: [
386
+ {
387
+ type: NodeType.Element,
388
+ tagName: 'title',
389
+ isSVG: true,
390
+ attributes: {
391
+ 'data-rrweb-id': titleId,
392
+ },
393
+ id: titleId,
394
+ childNodes: [
395
+ {
396
+ type: NodeType.Text,
397
+ textContent: title,
398
+ id: context.idSequence.next().value,
399
+ },
400
+ ],
401
+ },
402
+ {
403
+ type: NodeType.Element,
404
+ tagName: 'path',
405
+ isSVG: true,
406
+ attributes: {
407
+ d: path,
408
+ 'data-rrweb-id': pathId,
409
+ },
410
+ id: pathId,
411
+ childNodes: [],
412
+ },
413
+ ],
414
+ };
415
+ }
416
+ function filledStar(context) {
417
+ return makeStar('filled star', 'M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z', context);
418
+ }
419
+ function halfStar(context) {
420
+ return makeStar('half-filled star', 'M12,15.4V6.1L13.71,10.13L18.09,10.5L14.77,13.39L15.76,17.67M22,9.24L14.81,8.63L12,2L9.19,8.63L2,9.24L7.45,13.97L5.82,21L12,17.27L18.18,21L16.54,13.97L22,9.24Z', context);
421
+ }
422
+ function emptyStar(context) {
423
+ return makeStar('empty star', 'M12,15.39L8.24,17.66L9.23,13.38L5.91,10.5L10.29,10.13L12,6.09L13.71,10.13L18.09,10.5L14.77,13.38L15.76,17.66M22,9.24L14.81,8.63L12,2L9.19,8.63L2,9.24L7.45,13.97L5.82,21L12,17.27L18.18,21L16.54,13.97L22,9.24Z', context);
424
+ }
425
+ function makeRatingBar(wireframe, children, context) {
426
+ // max is the number of stars... and value is the number of stars to fill
427
+ // deliberate double equals, because we want to allow null and undefined
428
+ if (wireframe.value == null || wireframe.max == null) {
429
+ return makePlaceholderElement(wireframe, children, context);
430
+ }
431
+ const numberOfFilledStars = Math.floor(wireframe.value);
432
+ const numberOfHalfStars = wireframe.value - numberOfFilledStars > 0 ? 1 : 0;
433
+ const numberOfEmptyStars = wireframe.max - numberOfFilledStars - numberOfHalfStars;
434
+ const filledStars = Array(numberOfFilledStars)
435
+ .fill(undefined)
436
+ .map(() => filledStar(context));
437
+ const halfStars = Array(numberOfHalfStars)
438
+ .fill(undefined)
439
+ .map(() => halfStar(context));
440
+ const emptyStars = Array(numberOfEmptyStars)
441
+ .fill(undefined)
442
+ .map(() => emptyStar(context));
443
+ const ratingBarId = context.idSequence.next().value;
444
+ const ratingBar = {
445
+ type: NodeType.Element,
446
+ tagName: 'div',
447
+ id: ratingBarId,
448
+ attributes: {
449
+ style: asStyleString([
450
+ makeColorStyles(wireframe),
451
+ 'position: relative',
452
+ 'display: flex',
453
+ 'flex-direction: row',
454
+ 'padding: 2px 4px',
455
+ ]),
456
+ 'data-rrweb-id': ratingBarId,
457
+ },
458
+ childNodes: [...filledStars, ...halfStars, ...emptyStars],
459
+ };
460
+ return {
461
+ result: {
462
+ type: NodeType.Element,
463
+ tagName: 'div',
464
+ attributes: {
465
+ style: makeStylesString(wireframe),
466
+ 'data-rrweb-id': wireframe.id,
467
+ },
468
+ id: wireframe.id,
469
+ childNodes: [ratingBar, ...children],
470
+ },
471
+ context,
472
+ };
473
+ }
474
+ function makeProgressElement(wireframe, children, context) {
475
+ var _a, _b, _c, _d;
476
+ if (((_a = wireframe.style) === null || _a === void 0 ? void 0 : _a.bar) === 'circular') {
477
+ // value needs to be expressed as a number between 0 and 100
478
+ const max = wireframe.max || 1;
479
+ let value = wireframe.value || null;
480
+ if (_isPositiveInteger(value) && value <= max) {
481
+ value = (value / max) * 100;
482
+ }
483
+ else {
484
+ value = null;
485
+ }
486
+ const styleOverride = {
487
+ color: ((_b = wireframe.style) === null || _b === void 0 ? void 0 : _b.color) || FOREGROUND,
488
+ backgroundColor: ((_c = wireframe.style) === null || _c === void 0 ? void 0 : _c.backgroundColor) || BACKGROUND,
489
+ };
490
+ // if not _isPositiveInteger(value) then we render a spinner,
491
+ // so we need to add a style element with the spin keyframe
492
+ const stylingChildren = _isPositiveInteger(value)
493
+ ? []
494
+ : [
495
+ {
496
+ type: NodeType.Element,
497
+ tagName: 'style',
498
+ attributes: {
499
+ type: 'text/css',
500
+ },
501
+ id: context.idSequence.next().value,
502
+ childNodes: [
503
+ {
504
+ type: NodeType.Text,
505
+ textContent: `@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`,
506
+ id: context.idSequence.next().value,
507
+ },
508
+ ],
509
+ },
510
+ ];
511
+ const wrappingDivId = context.idSequence.next().value;
512
+ return {
513
+ result: {
514
+ type: NodeType.Element,
515
+ tagName: 'div',
516
+ attributes: {
517
+ style: makeMinimalStyles(wireframe),
518
+ 'data-rrweb-id': wireframe.id,
519
+ },
520
+ id: wireframe.id,
521
+ childNodes: [
522
+ {
523
+ type: NodeType.Element,
524
+ tagName: 'div',
525
+ attributes: {
526
+ // with no provided value we render a spinner
527
+ style: _isPositiveInteger(value)
528
+ ? makeDeterminateProgressStyles(wireframe, styleOverride)
529
+ : makeIndeterminateProgressStyles(wireframe, styleOverride),
530
+ 'data-rrweb-id': wrappingDivId,
531
+ },
532
+ id: wrappingDivId,
533
+ childNodes: stylingChildren,
534
+ },
535
+ ...children,
536
+ ],
537
+ },
538
+ context,
539
+ };
540
+ }
541
+ else if (((_d = wireframe.style) === null || _d === void 0 ? void 0 : _d.bar) === 'rating') {
542
+ return makeRatingBar(wireframe, children, context);
543
+ }
544
+ return {
545
+ result: {
546
+ type: NodeType.Element,
547
+ tagName: 'progress',
548
+ attributes: inputAttributes(wireframe),
549
+ id: wireframe.id,
550
+ childNodes: children,
551
+ },
552
+ context,
553
+ };
554
+ }
555
+ function makeToggleParts(wireframe, context) {
556
+ var _a, _b, _c, _d;
557
+ const togglePosition = wireframe.checked ? 'right' : 'left';
558
+ const defaultColor = wireframe.checked ? '#1d4aff' : BACKGROUND;
559
+ const sliderPartId = context.idSequence.next().value;
560
+ const handlePartId = context.idSequence.next().value;
561
+ return [
562
+ {
563
+ type: NodeType.Element,
564
+ tagName: 'div',
565
+ attributes: {
566
+ 'data-toggle-part': 'slider',
567
+ style: asStyleString([
568
+ 'position:absolute',
569
+ 'top:33%',
570
+ 'left:5%',
571
+ 'display:inline-block',
572
+ 'width:75%',
573
+ 'height:33%',
574
+ 'opacity: 0.2',
575
+ 'border-radius:7.5%',
576
+ `background-color:${((_a = wireframe.style) === null || _a === void 0 ? void 0 : _a.color) || defaultColor}`,
577
+ ]),
578
+ 'data-rrweb-id': sliderPartId,
579
+ },
580
+ id: sliderPartId,
581
+ childNodes: [],
582
+ },
583
+ {
584
+ type: NodeType.Element,
585
+ tagName: 'div',
586
+ attributes: {
587
+ 'data-toggle-part': 'handle',
588
+ style: asStyleString([
589
+ 'position:absolute',
590
+ 'top:1.5%',
591
+ `${togglePosition}:5%`,
592
+ 'display:flex',
593
+ 'align-items:center',
594
+ 'justify-content:center',
595
+ 'width:40%',
596
+ 'height:75%',
597
+ 'cursor:inherit',
598
+ 'border-radius:50%',
599
+ `background-color:${((_b = wireframe.style) === null || _b === void 0 ? void 0 : _b.color) || defaultColor}`,
600
+ `border:2px solid ${((_c = wireframe.style) === null || _c === void 0 ? void 0 : _c.borderColor) ||
601
+ ((_d = wireframe.style) === null || _d === void 0 ? void 0 : _d.color) ||
602
+ defaultColor}`,
603
+ ]),
604
+ 'data-rrweb-id': handlePartId,
605
+ },
606
+ id: handlePartId,
607
+ childNodes: [],
608
+ },
609
+ ];
610
+ }
611
+ function makeToggleElement(wireframe, context) {
612
+ const isLabelled = 'label' in wireframe;
613
+ const wrappingDivId = context.idSequence.next().value;
614
+ return {
615
+ result: {
616
+ type: NodeType.Element,
617
+ tagName: 'div',
618
+ attributes: {
619
+ // if labelled take up available space, otherwise use provided positioning
620
+ style: isLabelled
621
+ ? asStyleString(['height:100%', 'flex:1'])
622
+ : makePositionStyles(wireframe),
623
+ 'data-rrweb-id': wireframe.id,
624
+ },
625
+ id: wireframe.id,
626
+ childNodes: [
627
+ {
628
+ type: NodeType.Element,
629
+ tagName: 'div',
630
+ attributes: {
631
+ // relative position, fills parent
632
+ style: asStyleString([
633
+ 'position:relative',
634
+ 'width:100%',
635
+ 'height:100%',
636
+ ]),
637
+ 'data-rrweb-id': wrappingDivId,
638
+ },
639
+ id: wrappingDivId,
640
+ childNodes: makeToggleParts(wireframe, context),
641
+ },
642
+ ],
643
+ },
644
+ context,
645
+ };
646
+ }
647
+ function makeLabelledInput(wireframe, theInputElement, context) {
648
+ const theLabel = {
649
+ type: NodeType.Text,
650
+ textContent: wireframe.label || '',
651
+ id: context.idSequence.next().value,
652
+ };
653
+ const orderedChildren = wireframe.inputType === 'toggle'
654
+ ? [theLabel, theInputElement]
655
+ : [theInputElement, theLabel];
656
+ const labelId = context.idSequence.next().value;
657
+ return {
658
+ result: {
659
+ type: NodeType.Element,
660
+ tagName: 'label',
661
+ attributes: {
662
+ style: makeStylesString(wireframe),
663
+ 'data-rrweb-id': labelId,
664
+ },
665
+ id: labelId,
666
+ childNodes: orderedChildren,
667
+ },
668
+ context,
669
+ };
670
+ }
671
+ function makeInputElement(wireframe, children, context) {
672
+ if (!wireframe.inputType) {
673
+ return null;
674
+ }
675
+ if (wireframe.inputType === 'button') {
676
+ return makeButtonElement(wireframe, children, context);
677
+ }
678
+ if (wireframe.inputType === 'select') {
679
+ return makeSelectElement(wireframe, children, context);
680
+ }
681
+ if (wireframe.inputType === 'progress') {
682
+ return makeProgressElement(wireframe, children, context);
683
+ }
684
+ const theInputElement = wireframe.inputType === 'toggle'
685
+ ? makeToggleElement(wireframe, context)
686
+ : {
687
+ result: {
688
+ type: NodeType.Element,
689
+ tagName: 'input',
690
+ attributes: inputAttributes(wireframe),
691
+ id: wireframe.id,
692
+ childNodes: children,
693
+ },
694
+ context,
695
+ };
696
+ if (!theInputElement) {
697
+ return null;
698
+ }
699
+ if ('label' in wireframe) {
700
+ return makeLabelledInput(wireframe, theInputElement.result, theInputElement.context);
701
+ }
702
+ // when labelled no styles are needed, when un-labelled as here - we add the styling in.
703
+ theInputElement.result.attributes.style =
704
+ makeStylesString(wireframe);
705
+ return theInputElement;
706
+ }
707
+ function makeRectangleElement(wireframe, children, context) {
708
+ return {
709
+ result: {
710
+ type: NodeType.Element,
711
+ tagName: 'div',
712
+ attributes: {
713
+ style: makeStylesString(wireframe),
714
+ 'data-rrweb-id': wireframe.id,
715
+ },
716
+ id: wireframe.id,
717
+ childNodes: children,
718
+ },
719
+ context,
720
+ };
721
+ }
722
+ function chooseConverter(wireframe) {
723
+ // in theory type is always present
724
+ // but since this is coming over the wire we can't really be sure,
725
+ // and so we default to div
726
+ const converterType = wireframe.type || 'div';
727
+ const converterMapping = {
728
+ // KLUDGE: TS can't tell that the wireframe type of each function is safe based on the converter type
729
+ text: makeTextElement,
730
+ image: makeImageElement,
731
+ rectangle: makeRectangleElement,
732
+ div: makeDivElement,
733
+ input: makeInputElement,
734
+ radio_group: makeRadioGroupElement,
735
+ web_view: makeWebViewElement,
736
+ placeholder: makePlaceholderElement,
737
+ status_bar: makeStatusBar,
738
+ navigation_bar: makeNavigationBar,
739
+ screenshot: makeImageElement,
740
+ };
741
+ return converterMapping[converterType];
742
+ }
743
+ function convertWireframe(wireframe, context) {
744
+ var _a;
745
+ const children = convertWireframesFor(wireframe.childWireframes, context);
746
+ const converted = (_a = chooseConverter(wireframe)) === null || _a === void 0 ? void 0 : _a(wireframe, children.result, children.context);
747
+ return converted || null;
748
+ }
749
+ function convertWireframesFor(wireframes, context) {
750
+ if (!wireframes) {
751
+ return { result: [], context };
752
+ }
753
+ const result = [];
754
+ for (const wireframe of wireframes) {
755
+ const converted = convertWireframe(wireframe, context);
756
+ if (converted) {
757
+ result.push(converted.result);
758
+ context = converted.context;
759
+ }
760
+ }
761
+ return { result, context };
762
+ }
763
+ function isMobileIncrementalSnapshotEvent(x) {
764
+ const isIncrementalSnapshot = isObject(x) && 'type' in x && x.type === EventType.IncrementalSnapshot;
765
+ if (!isIncrementalSnapshot) {
766
+ return false;
767
+ }
768
+ const hasData = isObject(x) && 'data' in x;
769
+ const data = hasData ? x.data : null;
770
+ const hasMutationSource = isObject(data) &&
771
+ 'source' in data &&
772
+ data.source === IncrementalSource.Mutation;
773
+ const adds = isObject(data) && 'adds' in data && Array.isArray(data.adds)
774
+ ? data.adds
775
+ : null;
776
+ const updates = isObject(data) && 'updates' in data && Array.isArray(data.updates)
777
+ ? data.updates
778
+ : null;
779
+ const hasUpdatedWireframe = !!updates &&
780
+ updates.length > 0 &&
781
+ isObject(updates[0]) &&
782
+ 'wireframe' in updates[0];
783
+ const hasAddedWireframe = !!adds &&
784
+ adds.length > 0 &&
785
+ isObject(adds[0]) &&
786
+ 'wireframe' in adds[0];
787
+ return hasMutationSource && (hasAddedWireframe || hasUpdatedWireframe);
788
+ }
789
+ function chooseParentId(nodeType, providedParentId) {
790
+ return nodeType === 'screenshot' ? BODY_ID : providedParentId;
791
+ }
792
+ function makeIncrementalAdd(add, context) {
793
+ const converted = convertWireframe(add.wireframe, context);
794
+ if (!converted) {
795
+ return null;
796
+ }
797
+ const addition = {
798
+ parentId: chooseParentId(add.wireframe.type, add.parentId),
799
+ nextId: null,
800
+ node: converted.result,
801
+ };
802
+ const adds = [];
803
+ if (addition) {
804
+ const flattened = flattenMutationAdds(addition);
805
+ flattened.forEach(x => adds.push(x));
806
+ return adds;
807
+ }
808
+ return null;
809
+ }
810
+ /**
811
+ * When processing an update we remove the entire item, and then add it back in.
812
+ */
813
+ function makeIncrementalRemoveForUpdate(update) {
814
+ return {
815
+ parentId: chooseParentId(update.wireframe.type, update.parentId),
816
+ id: update.wireframe.id,
817
+ };
818
+ }
819
+ function isNode(x) {
820
+ // KLUDGE: really we should check that x.type is valid, but we're safe enough already
821
+ return isObject(x) && 'type' in x && 'id' in x;
822
+ }
823
+ function isNodeWithChildren(x) {
824
+ return isNode(x) && 'childNodes' in x && Array.isArray(x.childNodes);
825
+ }
826
+ /**
827
+ * when creating incremental adds we have to flatten the node tree structure
828
+ * there's no point, then keeping those child nodes in place
829
+ */
830
+ function cloneWithoutChildren(converted) {
831
+ const cloned = Object.assign({}, converted);
832
+ const clonedNode = Object.assign({}, converted.node);
833
+ if (isNodeWithChildren(clonedNode)) {
834
+ clonedNode.childNodes = [];
835
+ }
836
+ cloned.node = clonedNode;
837
+ return cloned;
838
+ }
839
+ function flattenMutationAdds(converted) {
840
+ const flattened = [];
841
+ flattened.push(cloneWithoutChildren(converted));
842
+ const node = converted.node;
843
+ const newParentId = converted.node.id;
844
+ if (isNodeWithChildren(node)) {
845
+ node.childNodes.forEach(child => {
846
+ flattened.push(cloneWithoutChildren({
847
+ parentId: newParentId,
848
+ nextId: null,
849
+ node: child,
850
+ }));
851
+ if (isNodeWithChildren(child)) {
852
+ flattened.push(...flattenMutationAdds({
853
+ parentId: newParentId,
854
+ nextId: null,
855
+ node: child,
856
+ }));
857
+ }
858
+ });
859
+ }
860
+ return flattened;
861
+ }
862
+ /**
863
+ * each update wireframe carries the entire tree because we don't want to diff on the client
864
+ * that means that we might create multiple mutations for the same node
865
+ * we only want to add it once, so we dedupe the mutations
866
+ * the app guarantees that for a given ID that is present more than once in a single snapshot
867
+ * every instance of that ID is identical
868
+ * it might change in the next snapshot but for a single incremental snapshot there is one
869
+ * and only one version of any given ID
870
+ */
871
+ function dedupeMutations(mutations) {
872
+ // KLUDGE: it's slightly yucky to stringify everything but since synthetic nodes
873
+ // introduce a new id, we can't just compare the id
874
+ const seen = new Set();
875
+ // in case later mutations are the ones we want to keep, we reverse the array
876
+ // this does help with the deduping, so, it's likely that the view for a single ID
877
+ // is not consistent over a snapshot, but it's cheap to reverse so :YOLO:
878
+ return mutations
879
+ .reverse()
880
+ .filter((mutation) => {
881
+ let toCompare;
882
+ if (isRemovedNodeMutation(mutation)) {
883
+ toCompare = JSON.stringify(mutation);
884
+ }
885
+ else {
886
+ // if this is a synthetic addition, then we need to ignore the id,
887
+ // since duplicates won't have duplicate ids
888
+ toCompare = JSON.stringify(Object.assign(Object.assign({}, mutation.node), { id: 0 }));
889
+ }
890
+ if (seen.has(toCompare)) {
891
+ return false;
892
+ }
893
+ seen.add(toCompare);
894
+ return true;
895
+ })
896
+ .reverse();
897
+ }
898
+ /**
899
+ * We want to ensure that any events don't use id = 0.
900
+ * They must always represent a valid ID from the dom, so we swap in the body id when the id = 0.
901
+ *
902
+ * For "removes", we don't need to do anything, the id of the element to be removed remains valid. We won't try and remove other elements that we added during transformation in order to show that element.
903
+ *
904
+ * "adds" are converted from wireframes to nodes and converted to `incrementalSnapshotEvent.adds`
905
+ *
906
+ * "updates" are converted to a remove and an add.
907
+ *
908
+ */
909
+ const makeIncrementalEvent = (mobileEvent) => {
910
+ const converted = mobileEvent;
911
+ if ('id' in converted.data && converted.data.id === 0) {
912
+ converted.data.id = BODY_ID;
913
+ }
914
+ if (isMobileIncrementalSnapshotEvent(mobileEvent)) {
915
+ const adds = [];
916
+ const removes = mobileEvent.data.removes || [];
917
+ if ('adds' in mobileEvent.data &&
918
+ Array.isArray(mobileEvent.data.adds)) {
919
+ const addsContext = {
920
+ timestamp: mobileEvent.timestamp,
921
+ idSequence: globalIdSequence,
922
+ };
923
+ mobileEvent.data.adds.forEach(add => {
924
+ var _a;
925
+ (_a = makeIncrementalAdd(add, addsContext)) === null || _a === void 0 ? void 0 : _a.forEach(x => adds.push(x));
926
+ });
927
+ }
928
+ if ('updates' in mobileEvent.data &&
929
+ Array.isArray(mobileEvent.data.updates)) {
930
+ const updatesContext = {
931
+ timestamp: mobileEvent.timestamp,
932
+ idSequence: globalIdSequence,
933
+ };
934
+ const updateAdditions = [];
935
+ mobileEvent.data.updates.forEach(update => {
936
+ var _a;
937
+ const removal = makeIncrementalRemoveForUpdate(update);
938
+ if (removal) {
939
+ removes.push(removal);
940
+ }
941
+ (_a = makeIncrementalAdd(update, updatesContext)) === null || _a === void 0 ? void 0 : _a.forEach(x => updateAdditions.push(x));
942
+ });
943
+ dedupeMutations(updateAdditions).forEach(x => adds.push(x));
944
+ }
945
+ converted.data = {
946
+ source: IncrementalSource.Mutation,
947
+ attributes: [],
948
+ texts: [],
949
+ adds: dedupeMutations(adds),
950
+ // TODO: this assumes that removes are processed before adds 🤞
951
+ removes: dedupeMutations(removes),
952
+ };
953
+ }
954
+ return converted;
955
+ };
956
+ function makeKeyboardParent() {
957
+ return {
958
+ type: NodeType.Element,
959
+ tagName: 'div',
960
+ attributes: {
961
+ 'data-render-reason': 'a fixed placeholder to contain the keyboard in the correct stacking position',
962
+ 'data-rrweb-id': KEYBOARD_PARENT_ID,
963
+ },
964
+ id: KEYBOARD_PARENT_ID,
965
+ childNodes: [],
966
+ };
967
+ }
968
+ function makeStatusBarNode(statusBar, context) {
969
+ const childNodes = statusBar
970
+ ? convertWireframesFor([statusBar], context).result
971
+ : [];
972
+ return {
973
+ type: NodeType.Element,
974
+ tagName: 'div',
975
+ attributes: {
976
+ 'data-rrweb-id': STATUS_BAR_PARENT_ID,
977
+ },
978
+ id: STATUS_BAR_PARENT_ID,
979
+ childNodes,
980
+ };
981
+ }
982
+ function makeNavBarNode(navigationBar, context) {
983
+ const childNodes = navigationBar
984
+ ? convertWireframesFor([navigationBar], context).result
985
+ : [];
986
+ return {
987
+ type: NodeType.Element,
988
+ tagName: 'div',
989
+ attributes: {
990
+ 'data-rrweb-id': NAVIGATION_BAR_PARENT_ID,
991
+ },
992
+ id: NAVIGATION_BAR_PARENT_ID,
993
+ childNodes,
994
+ };
995
+ }
996
+ function stripBarsFromWireframe(wireframe) {
997
+ if (wireframe.type === 'status_bar') {
998
+ return {
999
+ wireframe: undefined,
1000
+ statusBar: wireframe,
1001
+ navBar: undefined,
1002
+ };
1003
+ }
1004
+ else if (wireframe.type === 'navigation_bar') {
1005
+ return {
1006
+ wireframe: undefined,
1007
+ statusBar: undefined,
1008
+ navBar: wireframe,
1009
+ };
1010
+ }
1011
+ let statusBar;
1012
+ let navBar;
1013
+ const wireframeToReturn = Object.assign({}, wireframe);
1014
+ wireframeToReturn.childWireframes = [];
1015
+ for (const child of wireframe.childWireframes || []) {
1016
+ const { wireframe: childWireframe, statusBar: childStatusBar, navBar: childNavBar, } = stripBarsFromWireframe(child);
1017
+ statusBar = statusBar || childStatusBar;
1018
+ navBar = navBar || childNavBar;
1019
+ if (childWireframe) {
1020
+ wireframeToReturn.childWireframes.push(childWireframe);
1021
+ }
1022
+ }
1023
+ return { wireframe: wireframeToReturn, statusBar, navBar };
1024
+ }
1025
+ /**
1026
+ * We want to be able to place the status bar and navigation bar in the correct stacking order.
1027
+ * So, we lift them out of the tree, and return them separately.
1028
+ */
1029
+ function stripBarsFromWireframes(wireframes) {
1030
+ let statusBar;
1031
+ let navigationBar;
1032
+ const copiedNodes = [];
1033
+ wireframes.forEach(w => {
1034
+ const matches = stripBarsFromWireframe(w);
1035
+ if (matches.statusBar) {
1036
+ statusBar = matches.statusBar;
1037
+ }
1038
+ if (matches.navBar) {
1039
+ navigationBar = matches.navBar;
1040
+ }
1041
+ if (matches.wireframe) {
1042
+ copiedNodes.push(matches.wireframe);
1043
+ }
1044
+ });
1045
+ return { statusBar, navigationBar, appNodes: copiedNodes };
1046
+ }
1047
+ const makeFullEvent = (mobileEvent) => {
1048
+ // we can restart the id sequence on each full snapshot
1049
+ globalIdSequence = ids();
1050
+ if (!('wireframes' in mobileEvent.data)) {
1051
+ return mobileEvent;
1052
+ }
1053
+ const conversionContext = {
1054
+ timestamp: mobileEvent.timestamp,
1055
+ idSequence: globalIdSequence,
1056
+ };
1057
+ const { statusBar, navigationBar, appNodes } = stripBarsFromWireframes(mobileEvent.data.wireframes);
1058
+ const nodeGroups = {
1059
+ appNodes: convertWireframesFor(appNodes, conversionContext).result || [],
1060
+ statusBarNode: makeStatusBarNode(statusBar, conversionContext),
1061
+ navBarNode: makeNavBarNode(navigationBar, conversionContext),
1062
+ };
1063
+ return {
1064
+ type: EventType.FullSnapshot,
1065
+ timestamp: mobileEvent.timestamp,
1066
+ data: {
1067
+ node: {
1068
+ type: NodeType.Document,
1069
+ childNodes: [
1070
+ {
1071
+ type: NodeType.DocumentType,
1072
+ name: 'html',
1073
+ publicId: '',
1074
+ systemId: '',
1075
+ id: HTML_DOC_TYPE_ID,
1076
+ },
1077
+ {
1078
+ type: NodeType.Element,
1079
+ tagName: 'html',
1080
+ attributes: {
1081
+ style: makeHTMLStyles(),
1082
+ 'data-rrweb-id': HTML_ELEMENT_ID,
1083
+ },
1084
+ id: HTML_ELEMENT_ID,
1085
+ childNodes: [
1086
+ {
1087
+ type: NodeType.Element,
1088
+ tagName: 'head',
1089
+ attributes: { 'data-rrweb-id': HEAD_ID },
1090
+ id: HEAD_ID,
1091
+ childNodes: [makeCSSReset(conversionContext)],
1092
+ },
1093
+ {
1094
+ type: NodeType.Element,
1095
+ tagName: 'body',
1096
+ attributes: {
1097
+ style: makeBodyStyles(),
1098
+ 'data-rrweb-id': BODY_ID,
1099
+ },
1100
+ id: BODY_ID,
1101
+ childNodes: [
1102
+ // in the order they should stack if they ever clash
1103
+ // lower is higher in the stacking context
1104
+ ...nodeGroups.appNodes,
1105
+ makeKeyboardParent(),
1106
+ nodeGroups.navBarNode,
1107
+ nodeGroups.statusBarNode,
1108
+ ],
1109
+ },
1110
+ ],
1111
+ },
1112
+ ],
1113
+ id: DOCUMENT_ID,
1114
+ },
1115
+ initialOffset: {
1116
+ top: 0,
1117
+ left: 0,
1118
+ },
1119
+ },
1120
+ };
1121
+ };
1122
+ function makeCSSReset(context) {
1123
+ // we need to normalize CSS so browsers don't do unexpected things
1124
+ return {
1125
+ type: NodeType.Element,
1126
+ tagName: 'style',
1127
+ attributes: {
1128
+ type: 'text/css',
1129
+ },
1130
+ id: context.idSequence.next().value,
1131
+ childNodes: [
1132
+ {
1133
+ type: NodeType.Text,
1134
+ textContent: `
1135
+ body {
1136
+ margin: unset;
1137
+ }
1138
+ input, button, select, textarea {
1139
+ font: inherit;
1140
+ margin: 0;
1141
+ padding: 0;
1142
+ border: 0;
1143
+ outline: 0;
1144
+ background: transparent;
1145
+ padding-block: 0 !important;
1146
+ }
1147
+ .input:focus {
1148
+ outline: none;
1149
+ }
1150
+ img {
1151
+ border-style: none;
1152
+ }
1153
+ `,
1154
+ id: context.idSequence.next().value,
1155
+ },
1156
+ ],
1157
+ };
1158
+ }
1159
+
1160
+ export { BACKGROUND, KEYBOARD_ID, NAVIGATION_BAR_ID, STATUS_BAR_ID, STATUS_BAR_PARENT_ID, _isPositiveInteger, dataURIOrPNG, makeCustomEvent, makeDivElement, makeFullEvent, makeIncrementalEvent, makeMetaEvent, makePlaceholderElement, stripBarsFromWireframes };