@cloudcare/cocos-session-replay 0.1.0-alpha.6

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 (169) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +53 -0
  3. package/THIRD_PARTY_NOTICES +5 -0
  4. package/components/creator2/ReplayPrivacy.ts +23 -0
  5. package/components/creator3/ReplayPrivacy.ts +25 -0
  6. package/creator2/package.json +4 -0
  7. package/creator3/package.json +5 -0
  8. package/dist/creator2/core/client.d.ts +30 -0
  9. package/dist/creator2/core/client.d.ts.map +1 -0
  10. package/dist/creator2/core/client.js +115 -0
  11. package/dist/creator2/core/client.js.map +1 -0
  12. package/dist/creator2/core/replay-async-transport.d.ts +4 -0
  13. package/dist/creator2/core/replay-async-transport.d.ts.map +1 -0
  14. package/dist/creator2/core/replay-async-transport.js +67 -0
  15. package/dist/creator2/core/replay-async-transport.js.map +1 -0
  16. package/dist/creator2/core/replay-budget.d.ts +28 -0
  17. package/dist/creator2/core/replay-budget.d.ts.map +1 -0
  18. package/dist/creator2/core/replay-budget.js +126 -0
  19. package/dist/creator2/core/replay-budget.js.map +1 -0
  20. package/dist/creator2/core/replay-diagnostics.d.ts +36 -0
  21. package/dist/creator2/core/replay-diagnostics.d.ts.map +1 -0
  22. package/dist/creator2/core/replay-diagnostics.js +40 -0
  23. package/dist/creator2/core/replay-diagnostics.js.map +1 -0
  24. package/dist/creator2/core/replay-encoding.d.ts +31 -0
  25. package/dist/creator2/core/replay-encoding.d.ts.map +1 -0
  26. package/dist/creator2/core/replay-encoding.js +63 -0
  27. package/dist/creator2/core/replay-encoding.js.map +1 -0
  28. package/dist/creator2/core/replay-file.d.ts +5 -0
  29. package/dist/creator2/core/replay-file.d.ts.map +1 -0
  30. package/dist/creator2/core/replay-file.js +101 -0
  31. package/dist/creator2/core/replay-file.js.map +1 -0
  32. package/dist/creator2/core/replay-pixels.d.ts +3 -0
  33. package/dist/creator2/core/replay-pixels.d.ts.map +1 -0
  34. package/dist/creator2/core/replay-pixels.js +16 -0
  35. package/dist/creator2/core/replay-pixels.js.map +1 -0
  36. package/dist/creator2/core/replay-privacy-nodes.d.ts +14 -0
  37. package/dist/creator2/core/replay-privacy-nodes.d.ts.map +1 -0
  38. package/dist/creator2/core/replay-privacy-nodes.js +18 -0
  39. package/dist/creator2/core/replay-privacy-nodes.js.map +1 -0
  40. package/dist/creator2/core/replay-privacy.d.ts +15 -0
  41. package/dist/creator2/core/replay-privacy.d.ts.map +1 -0
  42. package/dist/creator2/core/replay-privacy.js +36 -0
  43. package/dist/creator2/core/replay-privacy.js.map +1 -0
  44. package/dist/creator2/core/replay.d.ts +102 -0
  45. package/dist/creator2/core/replay.d.ts.map +1 -0
  46. package/dist/creator2/core/replay.js +653 -0
  47. package/dist/creator2/core/replay.js.map +1 -0
  48. package/dist/creator2/core/transport.d.ts +3 -0
  49. package/dist/creator2/core/transport.d.ts.map +1 -0
  50. package/dist/creator2/core/transport.js +22 -0
  51. package/dist/creator2/core/transport.js.map +1 -0
  52. package/dist/creator2/core/types.d.ts +82 -0
  53. package/dist/creator2/core/types.d.ts.map +1 -0
  54. package/dist/creator2/core/types.js +3 -0
  55. package/dist/creator2/core/types.js.map +1 -0
  56. package/dist/creator2/core/validation.d.ts +13 -0
  57. package/dist/creator2/core/validation.d.ts.map +1 -0
  58. package/dist/creator2/core/validation.js +75 -0
  59. package/dist/creator2/core/validation.js.map +1 -0
  60. package/dist/creator2/core/version.d.ts +2 -0
  61. package/dist/creator2/core/version.d.ts.map +1 -0
  62. package/dist/creator2/core/version.js +5 -0
  63. package/dist/creator2/core/version.js.map +1 -0
  64. package/dist/creator2/creator2/capture.d.ts +17 -0
  65. package/dist/creator2/creator2/capture.d.ts.map +1 -0
  66. package/dist/creator2/creator2/capture.js +168 -0
  67. package/dist/creator2/creator2/capture.js.map +1 -0
  68. package/dist/creator2/creator2/index.d.ts +8 -0
  69. package/dist/creator2/creator2/index.d.ts.map +1 -0
  70. package/dist/creator2/creator2/index.js +19 -0
  71. package/dist/creator2/creator2/index.js.map +1 -0
  72. package/dist/creator2/creator2/pointer.d.ts +5 -0
  73. package/dist/creator2/creator2/pointer.d.ts.map +1 -0
  74. package/dist/creator2/creator2/pointer.js +49 -0
  75. package/dist/creator2/creator2/pointer.js.map +1 -0
  76. package/dist/creator2/creator2/transport.d.ts +8 -0
  77. package/dist/creator2/creator2/transport.d.ts.map +1 -0
  78. package/dist/creator2/creator2/transport.js +38 -0
  79. package/dist/creator2/creator2/transport.js.map +1 -0
  80. package/dist/creator3/core/client.d.ts +30 -0
  81. package/dist/creator3/core/client.d.ts.map +1 -0
  82. package/dist/creator3/core/client.js +97 -0
  83. package/dist/creator3/core/client.js.map +1 -0
  84. package/dist/creator3/core/replay-async-transport.d.ts +4 -0
  85. package/dist/creator3/core/replay-async-transport.d.ts.map +1 -0
  86. package/dist/creator3/core/replay-async-transport.js +16 -0
  87. package/dist/creator3/core/replay-async-transport.js.map +1 -0
  88. package/dist/creator3/core/replay-budget.d.ts +28 -0
  89. package/dist/creator3/core/replay-budget.d.ts.map +1 -0
  90. package/dist/creator3/core/replay-budget.js +118 -0
  91. package/dist/creator3/core/replay-budget.js.map +1 -0
  92. package/dist/creator3/core/replay-diagnostics.d.ts +36 -0
  93. package/dist/creator3/core/replay-diagnostics.d.ts.map +1 -0
  94. package/dist/creator3/core/replay-diagnostics.js +36 -0
  95. package/dist/creator3/core/replay-diagnostics.js.map +1 -0
  96. package/dist/creator3/core/replay-encoding.d.ts +31 -0
  97. package/dist/creator3/core/replay-encoding.d.ts.map +1 -0
  98. package/dist/creator3/core/replay-encoding.js +62 -0
  99. package/dist/creator3/core/replay-encoding.js.map +1 -0
  100. package/dist/creator3/core/replay-file.d.ts +5 -0
  101. package/dist/creator3/core/replay-file.d.ts.map +1 -0
  102. package/dist/creator3/core/replay-file.js +31 -0
  103. package/dist/creator3/core/replay-file.js.map +1 -0
  104. package/dist/creator3/core/replay-pixels.d.ts +3 -0
  105. package/dist/creator3/core/replay-pixels.d.ts.map +1 -0
  106. package/dist/creator3/core/replay-pixels.js +13 -0
  107. package/dist/creator3/core/replay-pixels.js.map +1 -0
  108. package/dist/creator3/core/replay-privacy-nodes.d.ts +14 -0
  109. package/dist/creator3/core/replay-privacy-nodes.d.ts.map +1 -0
  110. package/dist/creator3/core/replay-privacy-nodes.js +15 -0
  111. package/dist/creator3/core/replay-privacy-nodes.js.map +1 -0
  112. package/dist/creator3/core/replay-privacy.d.ts +15 -0
  113. package/dist/creator3/core/replay-privacy.d.ts.map +1 -0
  114. package/dist/creator3/core/replay-privacy.js +33 -0
  115. package/dist/creator3/core/replay-privacy.js.map +1 -0
  116. package/dist/creator3/core/replay.d.ts +102 -0
  117. package/dist/creator3/core/replay.d.ts.map +1 -0
  118. package/dist/creator3/core/replay.js +576 -0
  119. package/dist/creator3/core/replay.js.map +1 -0
  120. package/dist/creator3/core/transport.d.ts +3 -0
  121. package/dist/creator3/core/transport.d.ts.map +1 -0
  122. package/dist/creator3/core/transport.js +19 -0
  123. package/dist/creator3/core/transport.js.map +1 -0
  124. package/dist/creator3/core/types.d.ts +82 -0
  125. package/dist/creator3/core/types.d.ts.map +1 -0
  126. package/dist/creator3/core/types.js +2 -0
  127. package/dist/creator3/core/types.js.map +1 -0
  128. package/dist/creator3/core/validation.d.ts +13 -0
  129. package/dist/creator3/core/validation.d.ts.map +1 -0
  130. package/dist/creator3/core/validation.js +70 -0
  131. package/dist/creator3/core/validation.js.map +1 -0
  132. package/dist/creator3/core/version.d.ts +2 -0
  133. package/dist/creator3/core/version.d.ts.map +1 -0
  134. package/dist/creator3/core/version.js +2 -0
  135. package/dist/creator3/core/version.js.map +1 -0
  136. package/dist/creator3/creator3/capture.d.ts +17 -0
  137. package/dist/creator3/creator3/capture.d.ts.map +1 -0
  138. package/dist/creator3/creator3/capture.js +112 -0
  139. package/dist/creator3/creator3/capture.js.map +1 -0
  140. package/dist/creator3/creator3/index.d.ts +8 -0
  141. package/dist/creator3/creator3/index.d.ts.map +1 -0
  142. package/dist/creator3/creator3/index.js +14 -0
  143. package/dist/creator3/creator3/index.js.map +1 -0
  144. package/dist/creator3/creator3/pointer.d.ts +5 -0
  145. package/dist/creator3/creator3/pointer.d.ts.map +1 -0
  146. package/dist/creator3/creator3/pointer.js +29 -0
  147. package/dist/creator3/creator3/pointer.js.map +1 -0
  148. package/dist/creator3/creator3/replay-render-cycle.d.ts +9 -0
  149. package/dist/creator3/creator3/replay-render-cycle.d.ts.map +1 -0
  150. package/dist/creator3/creator3/replay-render-cycle.js +12 -0
  151. package/dist/creator3/creator3/replay-render-cycle.js.map +1 -0
  152. package/dist/creator3/creator3/transport.d.ts +9 -0
  153. package/dist/creator3/creator3/transport.d.ts.map +1 -0
  154. package/dist/creator3/creator3/transport.js +28 -0
  155. package/dist/creator3/creator3/transport.js.map +1 -0
  156. package/dist/creator3/package.json +1 -0
  157. package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosReplayBridge.java +168 -0
  158. package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosReplayImageJobs.java +55 -0
  159. package/native/ios/FTCocosReplayBridge.h +10 -0
  160. package/native/ios/FTCocosReplayBridge.m +275 -0
  161. package/native/ios/FTCocosReplayBridge.podspec +18 -0
  162. package/native/ios/FTCocosReplayImageJobs.h +11 -0
  163. package/native/ios/FTCocosReplayImageJobs.m +54 -0
  164. package/native/ios/Package.swift +26 -0
  165. package/native/ios/include/FTCocosReplayBridge.h +10 -0
  166. package/native/shared/FTReplayFileBridge.h +62 -0
  167. package/native/shared/FTReplayFileWorker.h +88 -0
  168. package/native-integration.json +9 -0
  169. package/package.json +58 -0
@@ -0,0 +1,653 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.FTSessionReplay = void 0;
51
+ exports.normalizeSaveImageV2Result = normalizeSaveImageV2Result;
52
+ exports.sampleReplayFrame = sampleReplayFrame;
53
+ exports.replayFramesLookSimilar = replayFramesLookSimilar;
54
+ exports.frameFingerprint = frameFingerprint;
55
+ exports.applyPrivacyRegions = applyPrivacyRegions;
56
+ var replay_encoding_js_1 = require("./replay-encoding.js");
57
+ var replay_budget_js_1 = require("./replay-budget.js");
58
+ var replay_diagnostics_js_1 = require("./replay-diagnostics.js");
59
+ var validation_js_1 = require("./validation.js");
60
+ var MAX_PENDING_POINTER_EVENTS = 512;
61
+ // Approximate comparisons must not hide small animations indefinitely.
62
+ var MAX_APPROXIMATE_STATIC_MS = 2000;
63
+ var SAMPLE_TILE_SIZE = 32;
64
+ /** Cocos canvas Session Replay controls. */
65
+ var FTSessionReplay = /** @class */ (function () {
66
+ function FTSessionReplay(transport, capture, pointerSource, validateNative) {
67
+ this.transport = transport;
68
+ this.capture = capture;
69
+ this.pointerSource = pointerSource;
70
+ this.validateNative = validateNative;
71
+ this.busy = false;
72
+ this.running = false;
73
+ this.mode = 'idle';
74
+ this.hybridReplayEnabled = false;
75
+ this.captureGeneration = 0;
76
+ this.config = { captureFps: 1 };
77
+ this.imagePolicy = (0, validation_js_1.replayImagePolicy)({});
78
+ this.budget = new replay_budget_js_1.FTReplayBudget(this.imagePolicy);
79
+ this.imagePolicyEnabled = false;
80
+ this.recordCounts = new Map();
81
+ this.recordTouches = false;
82
+ this.pendingPointerEvents = [];
83
+ }
84
+ /**
85
+ * Enables native Session Replay and starts Cocos canvas capture for a
86
+ * standalone application.
87
+ *
88
+ * @param config - Replay sampling, capture, and touch-privacy settings.
89
+ */
90
+ FTSessionReplay.prototype.start = function (config) {
91
+ var _a;
92
+ if (config === void 0) { config = {}; }
93
+ if (this.mode === 'hybrid') {
94
+ throw new Error('Hybrid Replay is managed by enterCocos() and leaveCocos()');
95
+ }
96
+ if (this.mode === 'standalone')
97
+ return;
98
+ (0, validation_js_1.samplingRate)(config.sampleRate, 'replay.sampleRate');
99
+ (0, validation_js_1.samplingRate)(config.sessionOnErrorSampleRate, 'replay.sessionOnErrorSampleRate');
100
+ this.configureCapture(config);
101
+ (_a = this.validateNative) === null || _a === void 0 ? void 0 : _a.call(this);
102
+ this.transport.invoke('replay.configure', config);
103
+ this.mode = 'standalone';
104
+ this.startCapture();
105
+ };
106
+ /** @internal */
107
+ FTSessionReplay.prototype.attachHybrid = function (config) {
108
+ if (this.mode === 'standalone') {
109
+ throw new Error('Standalone Replay and Hybrid Replay are mutually exclusive');
110
+ }
111
+ if (this.mode === 'hybrid')
112
+ return;
113
+ this.configureCapture(config || {});
114
+ this.hybridReplayEnabled = config !== undefined;
115
+ this.mode = 'hybrid';
116
+ };
117
+ /** @internal */
118
+ FTSessionReplay.prototype.detachHybrid = function () {
119
+ if (this.mode !== 'hybrid')
120
+ return;
121
+ this.stopCapture();
122
+ this.hybridReplayEnabled = false;
123
+ this.mode = 'idle';
124
+ };
125
+ /** @internal */
126
+ FTSessionReplay.prototype.enterHybrid = function () {
127
+ if (this.mode !== 'hybrid') {
128
+ throw new Error('Call attach() before entering Hybrid Replay');
129
+ }
130
+ if (!this.hybridReplayEnabled || this.running)
131
+ return;
132
+ this.transport.invoke('hybrid.setExternalRecorderActive', { active: true });
133
+ this.startCapture();
134
+ };
135
+ /** @internal */
136
+ FTSessionReplay.prototype.leaveHybrid = function () {
137
+ if (this.mode !== 'hybrid' || !this.hybridReplayEnabled)
138
+ return;
139
+ this.stopCapture();
140
+ this.transport.invoke('hybrid.setExternalRecorderActive', { active: false });
141
+ };
142
+ FTSessionReplay.prototype.configureCapture = function (config) {
143
+ if (config.touchPrivacy !== undefined && config.touchPrivacy !== 'show' && config.touchPrivacy !== 'hide') {
144
+ throw new TypeError("touchPrivacy must be either 'show' or 'hide'");
145
+ }
146
+ this.config = { captureFps: (0, validation_js_1.captureFps)(config.captureFps) };
147
+ this.imagePolicy = (0, validation_js_1.replayImagePolicy)(config);
148
+ this.budget = new replay_budget_js_1.FTReplayBudget(this.imagePolicy);
149
+ // Lossy image MIME must be validated against the deployed player before
150
+ // becoming an implicit default. Supplying imagePolicy opts into V2 now.
151
+ this.imagePolicyEnabled = config.imagePolicy !== undefined;
152
+ this.recordTouches = config.touchPrivacy === 'show';
153
+ };
154
+ /** Stops standalone Cocos canvas capture and native Session Replay. */
155
+ FTSessionReplay.prototype.stop = function () {
156
+ if (this.mode === 'hybrid') {
157
+ throw new Error('Hybrid Replay is managed by enterCocos() and leaveCocos()');
158
+ }
159
+ if (this.mode === 'idle')
160
+ return;
161
+ this.stopCapture();
162
+ this.transport.invoke('replay.stop');
163
+ this.mode = 'idle';
164
+ };
165
+ /**
166
+ * Changes the Session Replay privacy treatment for a Cocos node.
167
+ *
168
+ * `mask` obscures the node, `hide` removes the region from the replay, and
169
+ * `unmask` clears an earlier code override, restoring any ReplayPrivacy
170
+ * component rule or default input masking. Code overrides take priority over
171
+ * a component on the same node. The node must belong to the active Cocos scene.
172
+ *
173
+ * @param node - Cocos node whose rendered bounds receive the privacy rule.
174
+ * @param mode - Privacy treatment to apply.
175
+ */
176
+ FTSessionReplay.prototype.setPrivacy = function (node, mode) {
177
+ this.capture.setPrivacy(node, mode);
178
+ };
179
+ /** @internal */
180
+ FTSessionReplay.prototype.captureNow = function () {
181
+ return __awaiter(this, void 0, void 0, function () {
182
+ var generation, stored, context, pointerSize, contextKey, contextChanged, viewportSize, viewportChanged, priorityKey, now, decision, frame, fingerprint, privacyKey, sample, comparable, duplicate, approximatelyStatic, saved, pendingPointers, encoded, error_1;
183
+ var _a, _b, _c, _d;
184
+ return __generator(this, function (_e) {
185
+ switch (_e.label) {
186
+ case 0:
187
+ if (this.busy) {
188
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({ type: 'capture_skipped', timestamp: Date.now(), reason: 'busy' });
189
+ return [2 /*return*/, false];
190
+ }
191
+ generation = this.captureGeneration;
192
+ this.busy = true;
193
+ pointerSize = this.lastImageSize;
194
+ _e.label = 1;
195
+ case 1:
196
+ _e.trys.push([1, 5, 6, 11]);
197
+ context = this.readContext();
198
+ if (!context)
199
+ return [2 /*return*/, false];
200
+ contextKey = "".concat(context.applicationId, ":").concat(context.sessionId, ":").concat(context.viewId);
201
+ contextChanged = contextKey !== this.lastContextKey;
202
+ viewportSize = (_b = (_a = this.capture).getViewportSize) === null || _b === void 0 ? void 0 : _b.call(_a);
203
+ viewportChanged = this.lastViewportSize !== undefined
204
+ && viewportSize !== undefined
205
+ && (viewportSize.width !== this.lastViewportSize.width
206
+ || viewportSize.height !== this.lastViewportSize.height);
207
+ priorityKey = contextChanged
208
+ ? contextKey
209
+ : viewportChanged && viewportSize
210
+ ? "".concat(contextKey, ":").concat(viewportSize.width, "x").concat(viewportSize.height)
211
+ : undefined;
212
+ now = Date.now();
213
+ decision = this.budget.decide(now, priorityKey);
214
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({
215
+ type: 'capture_attempt',
216
+ timestamp: now,
217
+ usedBytes: decision.usedBytes,
218
+ maxBytesPerMinute: this.imagePolicy.maxBytesPerMinute,
219
+ });
220
+ if (!decision.allowed) {
221
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({
222
+ type: 'capture_skipped',
223
+ timestamp: now,
224
+ reason: decision.reason || 'budget',
225
+ });
226
+ return [2 /*return*/, pointerSize ? this.writePendingPointers(context, pointerSize.width, pointerSize.height) : false];
227
+ }
228
+ return [4 /*yield*/, this.capture.capture(decision.maxImageDimension)];
229
+ case 2:
230
+ frame = _e.sent();
231
+ if (generation !== this.captureGeneration)
232
+ return [2 /*return*/, false];
233
+ if (!frame)
234
+ return [2 /*return*/, false];
235
+ pointerSize = { width: frame.width, height: frame.height };
236
+ applyPrivacyRegions(frame.rgba, frame.width, frame.height, frame.privacyRegions || []);
237
+ fingerprint = frameFingerprint(frame.rgba);
238
+ privacyKey = privacyRegionsKey(frame.privacyRegions || []);
239
+ sample = sampleReplayFrame(frame.rgba, frame.width, frame.height);
240
+ comparable = contextKey === this.lastContextKey
241
+ && !viewportChanged
242
+ && frame.width === ((_c = this.lastFrameSample) === null || _c === void 0 ? void 0 : _c.width)
243
+ && frame.height === ((_d = this.lastFrameSample) === null || _d === void 0 ? void 0 : _d.height)
244
+ && privacyKey === this.lastPrivacyKey;
245
+ duplicate = comparable && fingerprint === this.lastFingerprint;
246
+ approximatelyStatic = comparable
247
+ && !duplicate
248
+ && this.lastImageTimestamp !== undefined
249
+ && now - this.lastImageTimestamp < MAX_APPROXIMATE_STATIC_MS
250
+ && replayFramesLookSimilar(this.lastFrameSample, sample);
251
+ if (duplicate || approximatelyStatic) {
252
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({
253
+ type: 'capture_skipped',
254
+ timestamp: now,
255
+ reason: duplicate ? 'dedupe' : 'approx_static',
256
+ });
257
+ return [2 /*return*/, this.writePendingPointers(context, frame.width, frame.height)];
258
+ }
259
+ return [4 /*yield*/, this.capture.persist(frame, fingerprint)];
260
+ case 3:
261
+ stored = _e.sent();
262
+ if (generation !== this.captureGeneration)
263
+ return [2 /*return*/, false];
264
+ return [4 /*yield*/, this.saveImage(stored, decision.compressionQuality, decision.maxFrameBytes)];
265
+ case 4:
266
+ saved = _e.sent();
267
+ if (generation !== this.captureGeneration)
268
+ return [2 /*return*/, false];
269
+ if (!saved) {
270
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({ type: 'capture_skipped', timestamp: now, reason: 'budget' });
271
+ return [2 /*return*/, this.writePendingPointers(context, frame.width, frame.height)];
272
+ }
273
+ pendingPointers = this.scaledPendingPointers(saved.width, saved.height);
274
+ encoded = (0, replay_encoding_js_1.encodeReplayImageRecord)(__assign(__assign({ context: context, resourceId: saved.resourceId, width: saved.width, height: saved.height }, (saved.mimeType ? { mimeType: saved.mimeType } : {})), { timestamp: stored.timestamp, contextChanged: contextChanged, pointerEvents: pendingPointers }));
275
+ this.transport.invoke('replay.writeSegment', {
276
+ viewId: context.viewId,
277
+ segment: encoded.segment,
278
+ });
279
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({
280
+ type: 'segment_encoded',
281
+ timestamp: now,
282
+ byteSize: (0, replay_diagnostics_js_1.replayUtf8ByteLength)(encoded.segment),
283
+ recordCount: encoded.recordCount,
284
+ pointerRecordCount: pendingPointers.length,
285
+ hasImage: true,
286
+ });
287
+ this.updateRecordCount(context.viewId, encoded.recordCount);
288
+ this.pendingPointerEvents.splice(0, pendingPointers.length);
289
+ this.budget.record(now, saved.byteSize, priorityKey);
290
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)(__assign(__assign({ type: 'image_saved', timestamp: now, byteSize: saved.byteSize, width: saved.width, height: saved.height }, (saved.mimeType ? { mimeType: saved.mimeType } : {})), { priority: decision.priority, byteSizeSource: saved.byteSizeSource }));
291
+ this.lastFingerprint = fingerprint;
292
+ this.lastContextKey = contextKey;
293
+ this.lastPrivacyKey = privacyKey;
294
+ this.lastFrameSample = sample;
295
+ this.lastImageTimestamp = now;
296
+ this.lastImageSize = { width: saved.width, height: saved.height };
297
+ this.lastViewportSize = viewportSize;
298
+ return [2 /*return*/, true];
299
+ case 5:
300
+ error_1 = _e.sent();
301
+ if (generation !== this.captureGeneration)
302
+ return [2 /*return*/, false];
303
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({ type: 'capture_skipped', timestamp: Date.now(), reason: 'error' });
304
+ console.error('[cocos-sdk] Session Replay frame dropped:', error_1);
305
+ if (context && pointerSize) {
306
+ try {
307
+ return [2 /*return*/, this.writePendingPointers(context, pointerSize.width, pointerSize.height)];
308
+ }
309
+ catch (pointerError) {
310
+ console.error('[cocos-sdk] Session Replay pointer segment dropped:', pointerError);
311
+ }
312
+ }
313
+ return [2 /*return*/, false];
314
+ case 6:
315
+ _e.trys.push([6, , 9, 10]);
316
+ if (!stored) return [3 /*break*/, 8];
317
+ return [4 /*yield*/, this.capture.disposeStoredFrame(stored)];
318
+ case 7:
319
+ _e.sent();
320
+ _e.label = 8;
321
+ case 8: return [3 /*break*/, 10];
322
+ case 9:
323
+ this.busy = false;
324
+ return [7 /*endfinally*/];
325
+ case 10: return [7 /*endfinally*/];
326
+ case 11: return [2 /*return*/];
327
+ }
328
+ });
329
+ });
330
+ };
331
+ FTSessionReplay.prototype.startCapture = function () {
332
+ var _this = this;
333
+ this.captureGeneration += 1;
334
+ this.running = true;
335
+ this.lastFingerprint = undefined;
336
+ this.lastContextKey = undefined;
337
+ this.lastPrivacyKey = undefined;
338
+ this.lastFrameSample = undefined;
339
+ this.lastImageTimestamp = undefined;
340
+ this.lastImageSize = undefined;
341
+ this.lastViewportSize = undefined;
342
+ this.budget.reset();
343
+ this.recordCounts.clear();
344
+ this.pendingPointerEvents = [];
345
+ if (this.recordTouches && this.pointerSource) {
346
+ this.stopPointerTracking = this.pointerSource.onReplayPointer(function (event) {
347
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({
348
+ type: 'pointer_received',
349
+ timestamp: event.timestamp,
350
+ eventType: event.eventType,
351
+ });
352
+ _this.pendingPointerEvents.push(event);
353
+ if (_this.pendingPointerEvents.length > MAX_PENDING_POINTER_EVENTS) {
354
+ _this.pendingPointerEvents.splice(0, _this.pendingPointerEvents.length - MAX_PENDING_POINTER_EVENTS);
355
+ }
356
+ });
357
+ }
358
+ this.schedule(0, this.captureGeneration);
359
+ };
360
+ FTSessionReplay.prototype.stopCapture = function () {
361
+ var _a;
362
+ var wasRunning = this.running;
363
+ this.captureGeneration += 1;
364
+ this.running = false;
365
+ (_a = this.stopPointerTracking) === null || _a === void 0 ? void 0 : _a.call(this);
366
+ this.stopPointerTracking = undefined;
367
+ this.pendingPointerEvents = [];
368
+ if (this.timer !== undefined)
369
+ clearTimeout(this.timer);
370
+ this.timer = undefined;
371
+ if (wasRunning)
372
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({ type: 'replay_stopped', timestamp: Date.now() });
373
+ };
374
+ FTSessionReplay.prototype.schedule = function (delay, generation) {
375
+ var _this = this;
376
+ if (!this.running)
377
+ return;
378
+ this.timer = setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
379
+ var error_2;
380
+ return __generator(this, function (_a) {
381
+ switch (_a.label) {
382
+ case 0:
383
+ if (!this.running || generation !== this.captureGeneration)
384
+ return [2 /*return*/];
385
+ _a.label = 1;
386
+ case 1:
387
+ _a.trys.push([1, 3, 4, 5]);
388
+ return [4 /*yield*/, this.captureNow()];
389
+ case 2:
390
+ _a.sent();
391
+ return [3 /*break*/, 5];
392
+ case 3:
393
+ error_2 = _a.sent();
394
+ console.error('[cocos-sdk] Session Replay frame dropped:', error_2);
395
+ return [3 /*break*/, 5];
396
+ case 4:
397
+ if (this.running && generation === this.captureGeneration) {
398
+ this.schedule(1000 / this.config.captureFps, generation);
399
+ }
400
+ return [7 /*endfinally*/];
401
+ case 5: return [2 /*return*/];
402
+ }
403
+ });
404
+ }); }, delay);
405
+ };
406
+ FTSessionReplay.prototype.readContext = function () {
407
+ var value = this.transport.invoke('replay.getContext');
408
+ return normalizeRumContext(value);
409
+ };
410
+ FTSessionReplay.prototype.saveImage = function (stored, compressionQuality, maxFrameBytes) {
411
+ return __awaiter(this, void 0, void 0, function () {
412
+ var value, error_3, resourceId;
413
+ return __generator(this, function (_a) {
414
+ switch (_a.label) {
415
+ case 0:
416
+ if (!(this.imagePolicyEnabled && this.supportsSaveImageV2 !== false)) return [3 /*break*/, 4];
417
+ _a.label = 1;
418
+ case 1:
419
+ _a.trys.push([1, 3, , 4]);
420
+ return [4 /*yield*/, this.invokeSaveImage('replay.saveImageV2', {
421
+ path: stored.path,
422
+ width: stored.width,
423
+ height: stored.height,
424
+ quality: compressionQuality,
425
+ maxFrameBytes: maxFrameBytes,
426
+ })];
427
+ case 2:
428
+ value = _a.sent();
429
+ if (value !== undefined) {
430
+ this.supportsSaveImageV2 = true;
431
+ if (isRejectedSaveImageV2(value))
432
+ return [2 /*return*/, undefined];
433
+ return [2 /*return*/, __assign(__assign({}, normalizeSaveImageV2Result(value)), { byteSizeSource: 'native' })];
434
+ }
435
+ this.supportsSaveImageV2 = false;
436
+ return [3 /*break*/, 4];
437
+ case 3:
438
+ error_3 = _a.sent();
439
+ if (!isUnknownBridgeMethod(error_3))
440
+ throw error_3;
441
+ this.supportsSaveImageV2 = false;
442
+ return [3 /*break*/, 4];
443
+ case 4: return [4 /*yield*/, this.invokeSaveImage('replay.saveImage', {
444
+ path: stored.path,
445
+ width: stored.width,
446
+ height: stored.height,
447
+ })];
448
+ case 5:
449
+ resourceId = _a.sent();
450
+ if (!resourceId)
451
+ return [2 /*return*/, undefined];
452
+ return [2 /*return*/, __assign({ resourceId: resourceId,
453
+ // V1 cannot report encoded size. A conservative frame-limit estimate
454
+ // still prevents configured captureFps from multiplying traffic freely.
455
+ byteSize: maxFrameBytes, width: stored.width, height: stored.height, byteSizeSource: 'frame_limit_estimate' }, (this.transport.platform === 'ios' ? { mimeType: 'image/png' } : {}))];
456
+ }
457
+ });
458
+ });
459
+ };
460
+ FTSessionReplay.prototype.invokeSaveImage = function (method, payload) {
461
+ return this.transport.invokeAsync
462
+ ? this.transport.invokeAsync(method, payload)
463
+ : Promise.resolve(this.transport.invoke(method, payload));
464
+ };
465
+ FTSessionReplay.prototype.writePendingPointers = function (context, width, height) {
466
+ var pointerEvents = this.scaledPendingPointers(width, height);
467
+ if (pointerEvents.length === 0)
468
+ return false;
469
+ var encoded = (0, replay_encoding_js_1.encodeReplayPointerRecords)({ context: context, pointerEvents: pointerEvents });
470
+ this.transport.invoke('replay.writeSegment', {
471
+ viewId: context.viewId,
472
+ segment: encoded.segment,
473
+ });
474
+ (0, replay_diagnostics_js_1.emitReplayDiagnostic)({
475
+ type: 'segment_encoded',
476
+ timestamp: Date.now(),
477
+ byteSize: (0, replay_diagnostics_js_1.replayUtf8ByteLength)(encoded.segment),
478
+ recordCount: encoded.recordCount,
479
+ pointerRecordCount: pointerEvents.length,
480
+ hasImage: false,
481
+ });
482
+ this.updateRecordCount(context.viewId, encoded.recordCount);
483
+ this.pendingPointerEvents.splice(0, pointerEvents.length);
484
+ return true;
485
+ };
486
+ FTSessionReplay.prototype.scaledPendingPointers = function (width, height) {
487
+ return this.pendingPointerEvents.map(function (event) { return ({
488
+ eventType: event.eventType,
489
+ pointerId: event.pointerId,
490
+ x: Math.round(clamp(event.normalizedX, 0, 1) * width),
491
+ y: Math.round(clamp(event.normalizedY, 0, 1) * height),
492
+ timestamp: event.timestamp,
493
+ }); });
494
+ };
495
+ FTSessionReplay.prototype.updateRecordCount = function (viewId, added) {
496
+ var count = (this.recordCounts.get(viewId) || 0) + added;
497
+ this.recordCounts.set(viewId, count);
498
+ this.transport.invoke('replay.setRecordCount', { viewId: viewId, count: count });
499
+ };
500
+ return FTSessionReplay;
501
+ }());
502
+ exports.FTSessionReplay = FTSessionReplay;
503
+ function normalizeRumContext(value) {
504
+ if (!value || typeof value !== 'object')
505
+ return undefined;
506
+ var context = value;
507
+ var applicationId = stringValue(context.applicationId, context.application_id);
508
+ var sessionId = stringValue(context.sessionId, context.session_id);
509
+ var viewId = stringValue(context.viewId, context.view_id);
510
+ if (!applicationId || !sessionId || !viewId)
511
+ return undefined;
512
+ var globalContext = context.globalContext;
513
+ return __assign({ applicationId: applicationId, sessionId: sessionId, viewId: viewId }, (globalContext && typeof globalContext === 'object'
514
+ ? { globalContext: globalContext }
515
+ : {}));
516
+ }
517
+ function stringValue() {
518
+ var values = [];
519
+ for (var _i = 0; _i < arguments.length; _i++) {
520
+ values[_i] = arguments[_i];
521
+ }
522
+ return values.find(function (value) { return typeof value === 'string' && value.length > 0; });
523
+ }
524
+ function normalizeSaveImageV2Result(value) {
525
+ if (!value || typeof value !== 'object')
526
+ throw new TypeError('Invalid replay.saveImageV2 result');
527
+ var result = value;
528
+ var resourceId = stringValue(result.resourceId, result.resource_id);
529
+ var byteSize = numberValue(result.byteSize, result.byte_size);
530
+ var width = numberValue(result.width);
531
+ var height = numberValue(result.height);
532
+ var mimeType = stringValue(result.mimeType, result.mime_type);
533
+ if (!resourceId || !isPositiveInteger(byteSize) || !isPositiveInteger(width) || !isPositiveInteger(height)) {
534
+ throw new TypeError('Invalid replay.saveImageV2 result');
535
+ }
536
+ if (mimeType && !mimeType.startsWith('image/')) {
537
+ throw new TypeError('Invalid replay.saveImageV2 mimeType');
538
+ }
539
+ return __assign({ resourceId: resourceId, byteSize: byteSize, width: width, height: height }, (mimeType ? { mimeType: mimeType } : {}));
540
+ }
541
+ function isRejectedSaveImageV2(value) {
542
+ return typeof value === 'object' && value !== null && value.accepted === false;
543
+ }
544
+ function isUnknownBridgeMethod(error) {
545
+ return error instanceof Error && /unknown bridge method|unrecognized selector/i.test(error.message);
546
+ }
547
+ function numberValue() {
548
+ var values = [];
549
+ for (var _i = 0; _i < arguments.length; _i++) {
550
+ values[_i] = arguments[_i];
551
+ }
552
+ return values.find(function (value) { return typeof value === 'number' && Number.isFinite(value); });
553
+ }
554
+ function isPositiveInteger(value) {
555
+ return value !== undefined && Number.isInteger(value) && value > 0;
556
+ }
557
+ function sampleReplayFrame(rgba, width, height, maximumSamples) {
558
+ if (maximumSamples === void 0) { maximumSamples = 4096; }
559
+ var pixelCount = width * height;
560
+ if (width < 1 || height < 1 || rgba.length < pixelCount * 4) {
561
+ throw new RangeError('Replay frame RGBA data does not match its dimensions');
562
+ }
563
+ var sampleCount = Math.min(maximumSamples, pixelCount);
564
+ var luminance = new Uint8Array(sampleCount);
565
+ var rgb = new Uint8Array(sampleCount * 3);
566
+ for (var index = 0; index < sampleCount; index += 1) {
567
+ var pixel = Math.min(pixelCount - 1, Math.floor(index * pixelCount / sampleCount));
568
+ var offset = pixel * 4;
569
+ rgb[index * 3] = rgba[offset];
570
+ rgb[index * 3 + 1] = rgba[offset + 1];
571
+ rgb[index * 3 + 2] = rgba[offset + 2];
572
+ luminance[index] = Math.round((rgba[offset] * 54 + rgba[offset + 1] * 183 + rgba[offset + 2] * 19) / 256);
573
+ }
574
+ return { width: width, height: height, luminance: luminance, rgb: rgb };
575
+ }
576
+ function replayFramesLookSimilar(previous, current) {
577
+ if (!previous
578
+ || previous.width !== current.width
579
+ || previous.height !== current.height
580
+ || previous.luminance.length !== current.luminance.length)
581
+ return false;
582
+ var totalDifference = 0;
583
+ var changedSamples = 0;
584
+ var tileColumns = Math.ceil(current.width / SAMPLE_TILE_SIZE);
585
+ var tileCount = tileColumns * Math.ceil(current.height / SAMPLE_TILE_SIZE);
586
+ var tileSamples = new Uint16Array(tileCount);
587
+ var tileChanges = new Uint16Array(tileCount);
588
+ for (var index = 0; index < current.luminance.length; index += 1) {
589
+ var difference = Math.abs(current.luminance[index] - previous.luminance[index]);
590
+ totalDifference += difference;
591
+ if (difference >= 12)
592
+ changedSamples += 1;
593
+ var pixel = Math.floor(index * current.width * current.height / current.luminance.length);
594
+ var tile = Math.floor(Math.floor(pixel / current.width) / SAMPLE_TILE_SIZE) * tileColumns
595
+ + Math.floor((pixel % current.width) / SAMPLE_TILE_SIZE);
596
+ tileSamples[tile] += 1;
597
+ var offset = index * 3;
598
+ // Color-only changes also matter, even when their luminance is unchanged.
599
+ if (Math.max(Math.abs(current.rgb[offset] - previous.rgb[offset]), Math.abs(current.rgb[offset + 1] - previous.rgb[offset + 1]), Math.abs(current.rgb[offset + 2] - previous.rgb[offset + 2])) >= 12)
600
+ tileChanges[tile] += 1;
601
+ }
602
+ for (var tile = 0; tile < tileCount; tile += 1) {
603
+ // A moving sprite can occupy much less than 1% of the whole screen.
604
+ if (tileChanges[tile] >= 2 && tileChanges[tile] / tileSamples[tile] >= 0.1)
605
+ return false;
606
+ }
607
+ var averageDifference = totalDifference / current.luminance.length;
608
+ var changedRatio = changedSamples / current.luminance.length;
609
+ return averageDifference <= 2.5 && changedRatio <= 0.01;
610
+ }
611
+ function privacyRegionsKey(regions) {
612
+ return regions.map(function (region) { return ("".concat(region.mode, ":").concat(region.x, ":").concat(region.y, ":").concat(region.width, ":").concat(region.height)); }).join('|');
613
+ }
614
+ function frameFingerprint(bytes) {
615
+ var hash = 2166136261;
616
+ // Sparse hashing can classify changes between sampled bytes as duplicates,
617
+ // bypassing both local motion detection and the approximate refresh limit.
618
+ // Read RGBA words without copying normal capture buffers to keep this cheap
619
+ // on the Cocos JS thread. Mix high bits down so every color channel matters.
620
+ var aligned = bytes.byteOffset % 4 === 0 ? bytes : bytes.slice();
621
+ var words = new Uint32Array(aligned.buffer, aligned.byteOffset, Math.floor(aligned.length / 4));
622
+ for (var index = 0; index < words.length; index += 1) {
623
+ hash = Math.imul(hash ^ words[index], 16777619);
624
+ hash ^= hash >>> 16;
625
+ }
626
+ for (var index = words.length * 4; index < bytes.length; index += 1) {
627
+ hash ^= bytes[index];
628
+ hash = Math.imul(hash, 16777619);
629
+ }
630
+ return "".concat(bytes.length.toString(16), "-").concat((hash >>> 0).toString(16));
631
+ }
632
+ function applyPrivacyRegions(rgba, width, height, regions) {
633
+ regions.forEach(function (region) {
634
+ var startX = clamp(Math.floor(region.x), 0, width);
635
+ var startY = clamp(Math.floor(region.y), 0, height);
636
+ var endX = clamp(Math.ceil(region.x + region.width), 0, width);
637
+ var endY = clamp(Math.ceil(region.y + region.height), 0, height);
638
+ var color = region.mode === 'hide' ? 0 : 128;
639
+ for (var y = startY; y < endY; y += 1) {
640
+ for (var x = startX; x < endX; x += 1) {
641
+ var offset = (y * width + x) * 4;
642
+ rgba[offset] = color;
643
+ rgba[offset + 1] = color;
644
+ rgba[offset + 2] = color;
645
+ rgba[offset + 3] = 255;
646
+ }
647
+ }
648
+ });
649
+ }
650
+ function clamp(value, minimum, maximum) {
651
+ return Math.min(maximum, Math.max(minimum, value));
652
+ }
653
+ //# sourceMappingURL=replay.js.map