@hot-updater/plugin-insights 1.0.0-rc.27 → 1.0.0-rc.29

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.
@@ -0,0 +1,35 @@
1
+ let _hot_updater_protocol = require("@hot-updater/protocol");
2
+ //#region src/client/errorDetails.ts
3
+ /** Bound the JSON wire size without rewriting the error text or splitting Unicode. */
4
+ const boundedText = (text, maxBytes) => {
5
+ if ((0, _hot_updater_protocol.getUtf8ByteLength)(JSON.stringify(text)) <= maxBytes) return text;
6
+ let result = "";
7
+ let bytes = 2 + (0, _hot_updater_protocol.getUtf8ByteLength)("…");
8
+ for (const character of text) {
9
+ bytes += (0, _hot_updater_protocol.getUtf8ByteLength)(JSON.stringify(character)) - 2;
10
+ if (bytes > maxBytes) break;
11
+ result += character;
12
+ }
13
+ return `${result}…`;
14
+ };
15
+ /** Error properties are not enumerable, so JSON.stringify(error) would lose them. */
16
+ const readErrorDetails = (error) => {
17
+ if (error == null) return {};
18
+ const object = typeof error === "object" ? error : null;
19
+ let message;
20
+ if (object && "message" in object && typeof object.message === "string") message = object.message;
21
+ else if (typeof error === "string") message = error;
22
+ else try {
23
+ message = JSON.stringify(error) ?? String(error);
24
+ } catch {
25
+ message = String(error);
26
+ }
27
+ const stack = object && "stack" in object && typeof object.stack === "string" ? object.stack : void 0;
28
+ return {
29
+ ...message ? { errorMessage: boundedText(message, 2048) } : {},
30
+ ...stack ? { errorStack: boundedText(stack, 4096) } : {}
31
+ };
32
+ };
33
+ //#endregion
34
+ exports.boundedText = boundedText;
35
+ exports.readErrorDetails = readErrorDetails;
@@ -0,0 +1,34 @@
1
+ import { getUtf8ByteLength } from "@hot-updater/protocol";
2
+ //#region src/client/errorDetails.ts
3
+ /** Bound the JSON wire size without rewriting the error text or splitting Unicode. */
4
+ const boundedText = (text, maxBytes) => {
5
+ if (getUtf8ByteLength(JSON.stringify(text)) <= maxBytes) return text;
6
+ let result = "";
7
+ let bytes = 2 + getUtf8ByteLength("…");
8
+ for (const character of text) {
9
+ bytes += getUtf8ByteLength(JSON.stringify(character)) - 2;
10
+ if (bytes > maxBytes) break;
11
+ result += character;
12
+ }
13
+ return `${result}…`;
14
+ };
15
+ /** Error properties are not enumerable, so JSON.stringify(error) would lose them. */
16
+ const readErrorDetails = (error) => {
17
+ if (error == null) return {};
18
+ const object = typeof error === "object" ? error : null;
19
+ let message;
20
+ if (object && "message" in object && typeof object.message === "string") message = object.message;
21
+ else if (typeof error === "string") message = error;
22
+ else try {
23
+ message = JSON.stringify(error) ?? String(error);
24
+ } catch {
25
+ message = String(error);
26
+ }
27
+ const stack = object && "stack" in object && typeof object.stack === "string" ? object.stack : void 0;
28
+ return {
29
+ ...message ? { errorMessage: boundedText(message, 2048) } : {},
30
+ ...stack ? { errorStack: boundedText(stack, 4096) } : {}
31
+ };
32
+ };
33
+ //#endregion
34
+ export { boundedText, readErrorDetails };
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errorDetails = require("./errorDetails.cjs");
2
3
  const require_eventId = require("./eventId.cjs");
3
4
  const require_sender = require("./sender.cjs");
4
5
  const require_state = require("./state.cjs");
@@ -45,8 +46,13 @@ const insights = (options = {}) => {
45
46
  const now = context.now();
46
47
  const installId = context.installId;
47
48
  const userId = state.readUserId();
49
+ const response = state.readHttpResponse(movement.channel, appVersion);
48
50
  const body = {
49
51
  ...movement,
52
+ ...response ? { metadata: {
53
+ ...movement.metadata,
54
+ httpResponse: response
55
+ } } : {},
50
56
  eventId: eventId?.(now, installId) ?? require_eventId.createUUIDv7(),
51
57
  installId,
52
58
  ...userId === null ? {} : { userId },
@@ -140,11 +146,13 @@ const insights = (options = {}) => {
140
146
  const onUpdateError = (error) => {
141
147
  if (error.stage === "check" && error.reason === "network") return;
142
148
  const targetBundleId = error.targetBundleId ?? error.bundleId;
143
- const failureKey = [
149
+ const details = require_errorDetails.readErrorDetails(error.cause);
150
+ const failureKey = JSON.stringify([
144
151
  error.stage,
145
152
  error.reason,
146
- targetBundleId
147
- ].join(" ");
153
+ targetBundleId,
154
+ details.errorMessage ?? null
155
+ ]);
148
156
  const event = createEvent({
149
157
  type: "UPDATE_FAILED",
150
158
  channel: error.channel,
@@ -156,6 +164,7 @@ const insights = (options = {}) => {
156
164
  metadata: { failure: {
157
165
  stage: error.stage,
158
166
  reason: error.reason,
167
+ ...details,
159
168
  ...error.resource === void 0 ? {} : { resource: error.resource },
160
169
  ...error.httpStatus === void 0 ? {} : { httpStatus: error.httpStatus },
161
170
  ...error.transport === void 0 ? {} : { transport: error.transport },
@@ -172,6 +181,17 @@ const insights = (options = {}) => {
172
181
  failureKey
173
182
  });
174
183
  };
184
+ const onHttpResponse = (response) => {
185
+ if (context === null || state === null || context.appVersion === null) return;
186
+ const body = response.body === null ? null : require_errorDetails.boundedText(response.body, 4096);
187
+ state.recordHttpResponse(context.getChannel(), context.appVersion, {
188
+ ...response,
189
+ path: require_errorDetails.boundedText(response.path, 1024),
190
+ body,
191
+ bodyTruncated: response.bodyTruncated || body !== response.body,
192
+ receivedAtMs: context.now()
193
+ });
194
+ };
175
195
  const admit = (body) => {
176
196
  const event = pending.get(body);
177
197
  if (event === void 0 || state === null || context === null) return false;
@@ -216,7 +236,8 @@ const insights = (options = {}) => {
216
236
  onAppReady,
217
237
  onUpdateCheck,
218
238
  onBundleDownloaded,
219
- onUpdateError
239
+ onUpdateError,
240
+ onHttpResponse
220
241
  };
221
242
  },
222
243
  setUser(user) {
@@ -1,3 +1,4 @@
1
+ import { boundedText, readErrorDetails } from "./errorDetails.mjs";
1
2
  import { createKeyedUUIDv7, createUUIDv7 } from "./eventId.mjs";
2
3
  import { createInsightsEventSender } from "./sender.mjs";
3
4
  import { createInsightsState, utcDay, utcDayStart } from "./state.mjs";
@@ -44,8 +45,13 @@ const insights = (options = {}) => {
44
45
  const now = context.now();
45
46
  const installId = context.installId;
46
47
  const userId = state.readUserId();
48
+ const response = state.readHttpResponse(movement.channel, appVersion);
47
49
  const body = {
48
50
  ...movement,
51
+ ...response ? { metadata: {
52
+ ...movement.metadata,
53
+ httpResponse: response
54
+ } } : {},
49
55
  eventId: eventId?.(now, installId) ?? createUUIDv7(),
50
56
  installId,
51
57
  ...userId === null ? {} : { userId },
@@ -139,11 +145,13 @@ const insights = (options = {}) => {
139
145
  const onUpdateError = (error) => {
140
146
  if (error.stage === "check" && error.reason === "network") return;
141
147
  const targetBundleId = error.targetBundleId ?? error.bundleId;
142
- const failureKey = [
148
+ const details = readErrorDetails(error.cause);
149
+ const failureKey = JSON.stringify([
143
150
  error.stage,
144
151
  error.reason,
145
- targetBundleId
146
- ].join(" ");
152
+ targetBundleId,
153
+ details.errorMessage ?? null
154
+ ]);
147
155
  const event = createEvent({
148
156
  type: "UPDATE_FAILED",
149
157
  channel: error.channel,
@@ -155,6 +163,7 @@ const insights = (options = {}) => {
155
163
  metadata: { failure: {
156
164
  stage: error.stage,
157
165
  reason: error.reason,
166
+ ...details,
158
167
  ...error.resource === void 0 ? {} : { resource: error.resource },
159
168
  ...error.httpStatus === void 0 ? {} : { httpStatus: error.httpStatus },
160
169
  ...error.transport === void 0 ? {} : { transport: error.transport },
@@ -171,6 +180,17 @@ const insights = (options = {}) => {
171
180
  failureKey
172
181
  });
173
182
  };
183
+ const onHttpResponse = (response) => {
184
+ if (context === null || state === null || context.appVersion === null) return;
185
+ const body = response.body === null ? null : boundedText(response.body, 4096);
186
+ state.recordHttpResponse(context.getChannel(), context.appVersion, {
187
+ ...response,
188
+ path: boundedText(response.path, 1024),
189
+ body,
190
+ bodyTruncated: response.bodyTruncated || body !== response.body,
191
+ receivedAtMs: context.now()
192
+ });
193
+ };
174
194
  const admit = (body) => {
175
195
  const event = pending.get(body);
176
196
  if (event === void 0 || state === null || context === null) return false;
@@ -215,7 +235,8 @@ const insights = (options = {}) => {
215
235
  onAppReady,
216
236
  onUpdateCheck,
217
237
  onBundleDownloaded,
218
- onUpdateError
238
+ onUpdateError,
239
+ onHttpResponse
219
240
  };
220
241
  },
221
242
  setUser(user) {
@@ -102,6 +102,24 @@ const createInsightsState = (storage) => {
102
102
  keys: [...keys, key].slice(-32)
103
103
  }));
104
104
  },
105
+ readHttpResponse(channel, appVersion) {
106
+ try {
107
+ const saved = JSON.parse(read("httpResponse") ?? "null");
108
+ if (!isRecord(saved) || saved.channel !== channel || saved.appVersion !== appVersion || !isRecord(saved.response)) return void 0;
109
+ const response = saved.response;
110
+ if (response.resource !== "catalog" && response.resource !== "artifact" || typeof response.path !== "string" || response.path.length > 1024 || typeof response.status !== "number" || !Number.isInteger(response.status) || response.status < 100 || response.status > 599 || !isNullableString(response.body) || JSON.stringify(response.body).length > 4096 || typeof response.bodyTruncated !== "boolean" || typeof response.receivedAtMs !== "number" || !Number.isSafeInteger(response.receivedAtMs) || response.receivedAtMs < 0) return void 0;
111
+ return response;
112
+ } catch {
113
+ return;
114
+ }
115
+ },
116
+ recordHttpResponse(channel, appVersion, response) {
117
+ write("httpResponse", JSON.stringify({
118
+ channel,
119
+ appVersion,
120
+ response
121
+ }));
122
+ },
105
123
  readUserId() {
106
124
  try {
107
125
  const user = JSON.parse(read("user") ?? "null");
@@ -102,6 +102,24 @@ const createInsightsState = (storage) => {
102
102
  keys: [...keys, key].slice(-32)
103
103
  }));
104
104
  },
105
+ readHttpResponse(channel, appVersion) {
106
+ try {
107
+ const saved = JSON.parse(read("httpResponse") ?? "null");
108
+ if (!isRecord(saved) || saved.channel !== channel || saved.appVersion !== appVersion || !isRecord(saved.response)) return void 0;
109
+ const response = saved.response;
110
+ if (response.resource !== "catalog" && response.resource !== "artifact" || typeof response.path !== "string" || response.path.length > 1024 || typeof response.status !== "number" || !Number.isInteger(response.status) || response.status < 100 || response.status > 599 || !isNullableString(response.body) || JSON.stringify(response.body).length > 4096 || typeof response.bodyTruncated !== "boolean" || typeof response.receivedAtMs !== "number" || !Number.isSafeInteger(response.receivedAtMs) || response.receivedAtMs < 0) return void 0;
111
+ return response;
112
+ } catch {
113
+ return;
114
+ }
115
+ },
116
+ recordHttpResponse(channel, appVersion, response) {
117
+ write("httpResponse", JSON.stringify({
118
+ channel,
119
+ appVersion,
120
+ response
121
+ }));
122
+ },
105
123
  readUserId() {
106
124
  try {
107
125
  const user = JSON.parse(read("user") ?? "null");
@@ -1,5 +1,6 @@
1
1
  import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, DatabaseBundleEventMetadata } from "./eventRow.cjs";
2
2
  import { InsightsScope } from "./modelTypes.cjs";
3
+ import { UpdateHttpResponse } from "@hot-updater/protocol";
3
4
  //#region src/server/domain.d.ts
4
5
  /**
5
6
  * Where an update failed and why, as a report carries it. Unknown values of
@@ -14,6 +15,9 @@ type BundleEventFailureInput = {
14
15
  readonly transport?: NonNullable<BundleEventFailure["transport"]>;
15
16
  /** The storage origin's error code, such as `ExpiredToken`. */
16
17
  readonly originCode?: string;
18
+ /** Original client error text, bounded to fit the event payload. */
19
+ readonly errorMessage?: string;
20
+ readonly errorStack?: string;
17
21
  /** Android's `ApplicationExitInfo` reason for the process before this one. */
18
22
  readonly previousProcessExit?: string;
19
23
  };
@@ -30,6 +34,9 @@ type CreateBundleEventRequestBase = {
30
34
  * server creates the ID, and a retry counts as another report.
31
35
  */
32
36
  readonly eventId?: string;
37
+ readonly metadata?: {
38
+ readonly httpResponse?: InsightsHttpResponse;
39
+ };
33
40
  readonly installId: string;
34
41
  readonly toBundleId: string;
35
42
  readonly userId?: string;
@@ -78,6 +85,8 @@ type CreateBundleEventRequest = (Movement & {
78
85
  });
79
86
  type ActiveInstallationWindow = "24h" | "7d" | "30d";
80
87
  type EventHistoryRow = {
88
+ readonly toReleaseId?: string;
89
+ readonly sdkVersion?: string;
81
90
  readonly id: string;
82
91
  readonly installId: string;
83
92
  readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED" | "UNCHANGED";
@@ -89,6 +98,7 @@ type EventHistoryRow = {
89
98
  readonly channel: string;
90
99
  readonly cohort: string;
91
100
  readonly receivedAtMs: number;
101
+ readonly httpResponse?: InsightsHttpResponse;
92
102
  /** `UPDATE_FAILED`: where the update failed and why. */
93
103
  readonly failure?: BundleEventFailureInput;
94
104
  /** `UPDATE_DOWNLOADED`: how the bundle arrived, when the client said. */
@@ -102,7 +112,11 @@ type InstallationHistoryRow = EventHistoryRow & {
102
112
  readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED";
103
113
  readonly fromBundleId: string;
104
114
  };
115
+ type InsightsHttpResponse = UpdateHttpResponse & {
116
+ readonly receivedAtMs: number;
117
+ };
105
118
  type InstallationRow = {
119
+ readonly httpResponse?: InsightsHttpResponse;
106
120
  readonly installId: string;
107
121
  readonly userId: string | null;
108
122
  readonly lastKnownBundleId: string;
@@ -146,4 +160,4 @@ type ReportingOverview = InsightsScope & {
146
160
  };
147
161
  };
148
162
  //#endregion
149
- export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InstallationHistoryRow, InstallationRow, ReportingOverview };
163
+ export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview };
@@ -1,5 +1,6 @@
1
1
  import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, DatabaseBundleEventMetadata } from "./eventRow.mjs";
2
2
  import { InsightsScope } from "./modelTypes.mjs";
3
+ import { UpdateHttpResponse } from "@hot-updater/protocol";
3
4
  //#region src/server/domain.d.ts
4
5
  /**
5
6
  * Where an update failed and why, as a report carries it. Unknown values of
@@ -14,6 +15,9 @@ type BundleEventFailureInput = {
14
15
  readonly transport?: NonNullable<BundleEventFailure["transport"]>;
15
16
  /** The storage origin's error code, such as `ExpiredToken`. */
16
17
  readonly originCode?: string;
18
+ /** Original client error text, bounded to fit the event payload. */
19
+ readonly errorMessage?: string;
20
+ readonly errorStack?: string;
17
21
  /** Android's `ApplicationExitInfo` reason for the process before this one. */
18
22
  readonly previousProcessExit?: string;
19
23
  };
@@ -30,6 +34,9 @@ type CreateBundleEventRequestBase = {
30
34
  * server creates the ID, and a retry counts as another report.
31
35
  */
32
36
  readonly eventId?: string;
37
+ readonly metadata?: {
38
+ readonly httpResponse?: InsightsHttpResponse;
39
+ };
33
40
  readonly installId: string;
34
41
  readonly toBundleId: string;
35
42
  readonly userId?: string;
@@ -78,6 +85,8 @@ type CreateBundleEventRequest = (Movement & {
78
85
  });
79
86
  type ActiveInstallationWindow = "24h" | "7d" | "30d";
80
87
  type EventHistoryRow = {
88
+ readonly toReleaseId?: string;
89
+ readonly sdkVersion?: string;
81
90
  readonly id: string;
82
91
  readonly installId: string;
83
92
  readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED" | "UNCHANGED";
@@ -89,6 +98,7 @@ type EventHistoryRow = {
89
98
  readonly channel: string;
90
99
  readonly cohort: string;
91
100
  readonly receivedAtMs: number;
101
+ readonly httpResponse?: InsightsHttpResponse;
92
102
  /** `UPDATE_FAILED`: where the update failed and why. */
93
103
  readonly failure?: BundleEventFailureInput;
94
104
  /** `UPDATE_DOWNLOADED`: how the bundle arrived, when the client said. */
@@ -102,7 +112,11 @@ type InstallationHistoryRow = EventHistoryRow & {
102
112
  readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED";
103
113
  readonly fromBundleId: string;
104
114
  };
115
+ type InsightsHttpResponse = UpdateHttpResponse & {
116
+ readonly receivedAtMs: number;
117
+ };
105
118
  type InstallationRow = {
119
+ readonly httpResponse?: InsightsHttpResponse;
106
120
  readonly installId: string;
107
121
  readonly userId: string | null;
108
122
  readonly lastKnownBundleId: string;
@@ -146,4 +160,4 @@ type ReportingOverview = InsightsScope & {
146
160
  };
147
161
  };
148
162
  //#endregion
149
- export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InstallationHistoryRow, InstallationRow, ReportingOverview };
163
+ export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview };
@@ -83,6 +83,8 @@ function readFailure(value) {
83
83
  const failure = isRecord(value) ? value : {};
84
84
  const httpStatus = failure.httpStatus;
85
85
  const originCode = readCode(failure.originCode);
86
+ const errorMessage = typeof failure.errorMessage === "string" && failure.errorMessage.length <= 2048 ? failure.errorMessage : void 0;
87
+ const errorStack = typeof failure.errorStack === "string" && failure.errorStack.length <= 4096 ? failure.errorStack : void 0;
86
88
  const previousProcessExit = readCode(failure.previousProcessExit);
87
89
  return {
88
90
  stage: oneOf(STAGES, failure.stage),
@@ -91,9 +93,22 @@ function readFailure(value) {
91
93
  ...Number.isSafeInteger(httpStatus) && httpStatus >= 100 && httpStatus <= 599 ? { httpStatus } : {},
92
94
  ...failure.transport == null ? {} : { transport: oneOf(TRANSPORTS, failure.transport) },
93
95
  ...originCode === void 0 ? {} : { originCode },
96
+ ...errorMessage === void 0 ? {} : { errorMessage },
97
+ ...errorStack === void 0 ? {} : { errorStack },
94
98
  ...previousProcessExit === void 0 ? {} : { previousProcessExit }
95
99
  };
96
100
  }
101
+ function readHttpResponse(value) {
102
+ if (!isRecord(value) || value.resource !== "catalog" && value.resource !== "artifact" || typeof value.path !== "string" || value.path.length > 1024 || !Number.isSafeInteger(value.status) || value.status < 100 || value.status > 599 || value.body !== null && (typeof value.body !== "string" || new TextEncoder().encode(JSON.stringify(value.body)).byteLength > 4096) || typeof value.bodyTruncated !== "boolean" || !Number.isSafeInteger(value.receivedAtMs) || value.receivedAtMs < 0) throw new require_errors.InsightsBadRequestError("Invalid HTTP response details");
103
+ return {
104
+ resource: value.resource,
105
+ path: value.path,
106
+ status: value.status,
107
+ body: value.body,
108
+ bodyTruncated: value.bodyTruncated,
109
+ receivedAtMs: value.receivedAtMs
110
+ };
111
+ }
97
112
  async function readBoundedText(request) {
98
113
  const contentLength = request.headers.get("content-length");
99
114
  const declaredByteLength = Number(contentLength);
@@ -129,7 +144,10 @@ function requireEvent(payload) {
129
144
  const platform = requireStringField(payload, "platform");
130
145
  if (platform !== "ios" && platform !== "android") throw new require_errors.InsightsBadRequestError("Invalid event field: platform");
131
146
  const eventId = readEventId(payload);
147
+ const metadata = readMetadata(payload);
148
+ const httpMetadata = metadata.httpResponse === void 0 ? {} : { httpResponse: readHttpResponse(metadata.httpResponse) };
132
149
  const base = {
150
+ ...metadata.httpResponse === void 0 ? {} : { metadata: httpMetadata },
133
151
  ...eventId === void 0 ? {} : { eventId },
134
152
  installId: requireIdentityField(payload, "installId"),
135
153
  toBundleId: requireStringField(payload, "toBundleId"),
@@ -153,11 +171,11 @@ function requireEvent(payload) {
153
171
  updateStrategy
154
172
  };
155
173
  };
156
- const metadata = readMetadata(payload);
157
174
  switch (type) {
158
175
  case "UPDATE_DOWNLOADED": {
159
176
  const delivery = metadata.delivery;
160
177
  const read = {
178
+ ...httpMetadata,
161
179
  ...delivery == null ? {} : { delivery: oneOf(DELIVERIES, delivery) },
162
180
  ...metadata.patchFallback === true ? { patchFallback: true } : {}
163
181
  };
@@ -176,13 +194,19 @@ function requireEvent(payload) {
176
194
  return {
177
195
  ...movement(),
178
196
  type,
179
- ...previousProcessExit === void 0 ? {} : { metadata: { previousProcessExit } }
197
+ ...previousProcessExit === void 0 ? {} : { metadata: {
198
+ ...httpMetadata,
199
+ previousProcessExit
200
+ } }
180
201
  };
181
202
  }
182
203
  case "UPDATE_FAILED": return {
183
204
  ...movement(),
184
205
  type,
185
- metadata: { failure: readFailure(metadata.failure) }
206
+ metadata: {
207
+ ...httpMetadata,
208
+ failure: readFailure(metadata.failure)
209
+ }
186
210
  };
187
211
  case "UNCHANGED":
188
212
  if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new require_errors.InsightsBadRequestError("Invalid unchanged event shape");
@@ -212,7 +236,16 @@ function createBundleEventRow(input) {
212
236
  to_release_id: input.toReleaseId,
213
237
  user_id: input.userId ?? null
214
238
  };
239
+ const response = input.metadata?.httpResponse;
215
240
  const metadata = {
241
+ ...response ? { http_response: {
242
+ resource: response.resource,
243
+ path: response.path,
244
+ status: response.status,
245
+ body: response.body,
246
+ body_truncated: response.bodyTruncated,
247
+ received_at_ms: response.receivedAtMs
248
+ } } : {},
216
249
  cohort: input.cohort,
217
250
  fingerprint_hash: input.fingerprintHash,
218
251
  sdk_version: input.sdkVersion ?? null,
@@ -261,6 +294,8 @@ function createBundleEventRow(input) {
261
294
  failure: {
262
295
  stage: failure.stage,
263
296
  reason: failure.reason,
297
+ ...failure.errorMessage === void 0 ? {} : { error_message: failure.errorMessage },
298
+ ...failure.errorStack === void 0 ? {} : { error_stack: failure.errorStack },
264
299
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
265
300
  ...failure.httpStatus === void 0 ? {} : { http_status: failure.httpStatus },
266
301
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
@@ -83,6 +83,8 @@ function readFailure(value) {
83
83
  const failure = isRecord(value) ? value : {};
84
84
  const httpStatus = failure.httpStatus;
85
85
  const originCode = readCode(failure.originCode);
86
+ const errorMessage = typeof failure.errorMessage === "string" && failure.errorMessage.length <= 2048 ? failure.errorMessage : void 0;
87
+ const errorStack = typeof failure.errorStack === "string" && failure.errorStack.length <= 4096 ? failure.errorStack : void 0;
86
88
  const previousProcessExit = readCode(failure.previousProcessExit);
87
89
  return {
88
90
  stage: oneOf(STAGES, failure.stage),
@@ -91,9 +93,22 @@ function readFailure(value) {
91
93
  ...Number.isSafeInteger(httpStatus) && httpStatus >= 100 && httpStatus <= 599 ? { httpStatus } : {},
92
94
  ...failure.transport == null ? {} : { transport: oneOf(TRANSPORTS, failure.transport) },
93
95
  ...originCode === void 0 ? {} : { originCode },
96
+ ...errorMessage === void 0 ? {} : { errorMessage },
97
+ ...errorStack === void 0 ? {} : { errorStack },
94
98
  ...previousProcessExit === void 0 ? {} : { previousProcessExit }
95
99
  };
96
100
  }
101
+ function readHttpResponse(value) {
102
+ if (!isRecord(value) || value.resource !== "catalog" && value.resource !== "artifact" || typeof value.path !== "string" || value.path.length > 1024 || !Number.isSafeInteger(value.status) || value.status < 100 || value.status > 599 || value.body !== null && (typeof value.body !== "string" || new TextEncoder().encode(JSON.stringify(value.body)).byteLength > 4096) || typeof value.bodyTruncated !== "boolean" || !Number.isSafeInteger(value.receivedAtMs) || value.receivedAtMs < 0) throw new InsightsBadRequestError("Invalid HTTP response details");
103
+ return {
104
+ resource: value.resource,
105
+ path: value.path,
106
+ status: value.status,
107
+ body: value.body,
108
+ bodyTruncated: value.bodyTruncated,
109
+ receivedAtMs: value.receivedAtMs
110
+ };
111
+ }
97
112
  async function readBoundedText(request) {
98
113
  const contentLength = request.headers.get("content-length");
99
114
  const declaredByteLength = Number(contentLength);
@@ -129,7 +144,10 @@ function requireEvent(payload) {
129
144
  const platform = requireStringField(payload, "platform");
130
145
  if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid event field: platform");
131
146
  const eventId = readEventId(payload);
147
+ const metadata = readMetadata(payload);
148
+ const httpMetadata = metadata.httpResponse === void 0 ? {} : { httpResponse: readHttpResponse(metadata.httpResponse) };
132
149
  const base = {
150
+ ...metadata.httpResponse === void 0 ? {} : { metadata: httpMetadata },
133
151
  ...eventId === void 0 ? {} : { eventId },
134
152
  installId: requireIdentityField(payload, "installId"),
135
153
  toBundleId: requireStringField(payload, "toBundleId"),
@@ -153,11 +171,11 @@ function requireEvent(payload) {
153
171
  updateStrategy
154
172
  };
155
173
  };
156
- const metadata = readMetadata(payload);
157
174
  switch (type) {
158
175
  case "UPDATE_DOWNLOADED": {
159
176
  const delivery = metadata.delivery;
160
177
  const read = {
178
+ ...httpMetadata,
161
179
  ...delivery == null ? {} : { delivery: oneOf(DELIVERIES, delivery) },
162
180
  ...metadata.patchFallback === true ? { patchFallback: true } : {}
163
181
  };
@@ -176,13 +194,19 @@ function requireEvent(payload) {
176
194
  return {
177
195
  ...movement(),
178
196
  type,
179
- ...previousProcessExit === void 0 ? {} : { metadata: { previousProcessExit } }
197
+ ...previousProcessExit === void 0 ? {} : { metadata: {
198
+ ...httpMetadata,
199
+ previousProcessExit
200
+ } }
180
201
  };
181
202
  }
182
203
  case "UPDATE_FAILED": return {
183
204
  ...movement(),
184
205
  type,
185
- metadata: { failure: readFailure(metadata.failure) }
206
+ metadata: {
207
+ ...httpMetadata,
208
+ failure: readFailure(metadata.failure)
209
+ }
186
210
  };
187
211
  case "UNCHANGED":
188
212
  if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new InsightsBadRequestError("Invalid unchanged event shape");
@@ -212,7 +236,16 @@ function createBundleEventRow(input) {
212
236
  to_release_id: input.toReleaseId,
213
237
  user_id: input.userId ?? null
214
238
  };
239
+ const response = input.metadata?.httpResponse;
215
240
  const metadata = {
241
+ ...response ? { http_response: {
242
+ resource: response.resource,
243
+ path: response.path,
244
+ status: response.status,
245
+ body: response.body,
246
+ body_truncated: response.bodyTruncated,
247
+ received_at_ms: response.receivedAtMs
248
+ } } : {},
216
249
  cohort: input.cohort,
217
250
  fingerprint_hash: input.fingerprintHash,
218
251
  sdk_version: input.sdkVersion ?? null,
@@ -261,6 +294,8 @@ function createBundleEventRow(input) {
261
294
  failure: {
262
295
  stage: failure.stage,
263
296
  reason: failure.reason,
297
+ ...failure.errorMessage === void 0 ? {} : { error_message: failure.errorMessage },
298
+ ...failure.errorStack === void 0 ? {} : { error_stack: failure.errorStack },
264
299
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
265
300
  ...failure.httpStatus === void 0 ? {} : { http_status: failure.httpStatus },
266
301
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
@@ -2,9 +2,10 @@ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
2
2
  //#region src/server/eventRow.ts
3
3
  const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(value[key]);
4
4
  const isString = (value) => typeof value === "string";
5
+ const isDatabaseHttpResponse = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
5
6
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
6
- const isDatabaseBundleEventFailure = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "previous_process_exit", isString);
7
- const isDatabaseBundleEventMetadata = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
7
+ const isDatabaseBundleEventFailure = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString) && isOptional(value, "previous_process_exit", isString);
8
+ const isDatabaseBundleEventMetadata = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
8
9
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9
10
  const isIdentity = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
10
11
  const isTextOrNull = (value) => value === null || typeof value === "string";
@@ -14,9 +14,19 @@ type BundleEventFailure = DatabaseJsonObject & {
14
14
  readonly transport?: "timeout" | "dns" | "tls" | "connection" | "offline" | "cancelled" | "unknown";
15
15
  /** The storage origin's error code, such as `ExpiredToken`. */
16
16
  readonly origin_code?: string;
17
+ readonly error_message?: string;
18
+ readonly error_stack?: string;
17
19
  /** Android's `ApplicationExitInfo` reason for the process before this one. */
18
20
  readonly previous_process_exit?: string;
19
21
  };
22
+ type DatabaseHttpResponse = DatabaseJsonObject & {
23
+ readonly resource: "catalog" | "artifact";
24
+ readonly path: string;
25
+ readonly status: number;
26
+ readonly body: string | null;
27
+ readonly body_truncated: boolean;
28
+ readonly received_at_ms: number;
29
+ };
20
30
  /** Ancillary report data; queryable identity and lifecycle fields stay on the row. */
21
31
  type DatabaseBundleEventMetadata = DatabaseJsonObject & {
22
32
  readonly cohort: string;
@@ -25,6 +35,7 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
25
35
  readonly sdk_version: string | null;
26
36
  /** `UPDATE_FAILED`: what failed. */
27
37
  readonly failure?: BundleEventFailure;
38
+ readonly http_response?: DatabaseHttpResponse;
28
39
  /** `UPDATE_DOWNLOADED`: how the bundle arrived. */
29
40
  readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
30
41
  /** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
@@ -61,4 +72,4 @@ type BundleEventRow = BundleEventRowBase & ({
61
72
  readonly from_bundle_id: null;
62
73
  });
63
74
  //#endregion
64
- export { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata };
75
+ export { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata, DatabaseHttpResponse };
@@ -14,9 +14,19 @@ type BundleEventFailure = DatabaseJsonObject & {
14
14
  readonly transport?: "timeout" | "dns" | "tls" | "connection" | "offline" | "cancelled" | "unknown";
15
15
  /** The storage origin's error code, such as `ExpiredToken`. */
16
16
  readonly origin_code?: string;
17
+ readonly error_message?: string;
18
+ readonly error_stack?: string;
17
19
  /** Android's `ApplicationExitInfo` reason for the process before this one. */
18
20
  readonly previous_process_exit?: string;
19
21
  };
22
+ type DatabaseHttpResponse = DatabaseJsonObject & {
23
+ readonly resource: "catalog" | "artifact";
24
+ readonly path: string;
25
+ readonly status: number;
26
+ readonly body: string | null;
27
+ readonly body_truncated: boolean;
28
+ readonly received_at_ms: number;
29
+ };
20
30
  /** Ancillary report data; queryable identity and lifecycle fields stay on the row. */
21
31
  type DatabaseBundleEventMetadata = DatabaseJsonObject & {
22
32
  readonly cohort: string;
@@ -25,6 +35,7 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
25
35
  readonly sdk_version: string | null;
26
36
  /** `UPDATE_FAILED`: what failed. */
27
37
  readonly failure?: BundleEventFailure;
38
+ readonly http_response?: DatabaseHttpResponse;
28
39
  /** `UPDATE_DOWNLOADED`: how the bundle arrived. */
29
40
  readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
30
41
  /** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
@@ -61,4 +72,4 @@ type BundleEventRow = BundleEventRowBase & ({
61
72
  readonly from_bundle_id: null;
62
73
  });
63
74
  //#endregion
64
- export { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata };
75
+ export { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata, DatabaseHttpResponse };
@@ -2,9 +2,10 @@ import { DatabaseAdapterInputError, isDatabaseJsonObject } from "@hot-updater/pl
2
2
  //#region src/server/eventRow.ts
3
3
  const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(value[key]);
4
4
  const isString = (value) => typeof value === "string";
5
+ const isDatabaseHttpResponse = (value) => isDatabaseJsonObject(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
5
6
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
6
- const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "previous_process_exit", isString);
7
- const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
7
+ const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString) && isOptional(value, "previous_process_exit", isString);
8
+ const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
8
9
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9
10
  const isIdentity = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
10
11
  const isTextOrNull = (value) => value === null || typeof value === "string";
@@ -2,7 +2,7 @@ import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage,
2
2
  import { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.cjs";
3
3
  import { DEFAULT_INSIGHTS_RETENTION, InsightsRetention, InsightsSchema, createInsightsSchema, insightsSchema } from "./schema.cjs";
4
4
  import { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput } from "./reads.cjs";
5
- import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.cjs";
5
+ import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.cjs";
6
6
  import { InsightsBadRequestError } from "./errors.cjs";
7
7
  import { createInsightsModel } from "./model.cjs";
8
8
  import { InsightsEventPageInput, InsightsInstallationEventPageInput, InsightsProvider, InsightsUserInstallationPageInput } from "./types.cjs";
@@ -392,4 +392,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
392
392
  endpoints: PluginEndpoint[];
393
393
  }, {}>;
394
394
  //#endregion
395
- export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
395
+ export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
@@ -2,7 +2,7 @@ import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage,
2
2
  import { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.mjs";
3
3
  import { DEFAULT_INSIGHTS_RETENTION, InsightsRetention, InsightsSchema, createInsightsSchema, insightsSchema } from "./schema.mjs";
4
4
  import { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput } from "./reads.mjs";
5
- import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
5
+ import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
6
6
  import { InsightsBadRequestError } from "./errors.mjs";
7
7
  import { createInsightsModel } from "./model.mjs";
8
8
  import { InsightsEventPageInput, InsightsInstallationEventPageInput, InsightsProvider, InsightsUserInstallationPageInput } from "./types.mjs";
@@ -392,4 +392,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
392
392
  endpoints: PluginEndpoint[];
393
393
  }, {}>;
394
394
  //#endregion
395
- export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
395
+ export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
@@ -147,6 +147,8 @@ const assertEventRows = (rows, input) => {
147
147
  const toFailure = (failure) => ({
148
148
  stage: failure.stage,
149
149
  reason: failure.reason,
150
+ ...failure.error_message === void 0 ? {} : { errorMessage: failure.error_message },
151
+ ...failure.error_stack === void 0 ? {} : { errorStack: failure.error_stack },
150
152
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
151
153
  ...failure.http_status === void 0 ? {} : { httpStatus: failure.http_status },
152
154
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
@@ -155,12 +157,22 @@ const toFailure = (failure) => ({
155
157
  });
156
158
  /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
157
159
  const eventDetails = ({ type, metadata }) => ({
160
+ ...metadata.http_response !== void 0 ? { httpResponse: {
161
+ resource: metadata.http_response.resource,
162
+ path: metadata.http_response.path,
163
+ status: metadata.http_response.status,
164
+ body: metadata.http_response.body,
165
+ bodyTruncated: metadata.http_response.body_truncated,
166
+ receivedAtMs: metadata.http_response.received_at_ms
167
+ } } : {},
158
168
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
159
169
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
160
170
  ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
161
171
  ...type === "RECOVERED" && metadata.previous_process_exit !== void 0 ? { previousProcessExit: metadata.previous_process_exit } : {}
162
172
  });
163
173
  const toEventHistoryRow = (row) => ({
174
+ ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
175
+ ...row.metadata.sdk_version == null ? {} : { sdkVersion: row.metadata.sdk_version },
164
176
  appVersion: row.app_version,
165
177
  channel: row.channel,
166
178
  cohort: row.metadata.cohort,
@@ -175,6 +187,7 @@ const toEventHistoryRow = (row) => ({
175
187
  ...eventDetails(row)
176
188
  });
177
189
  const toInstallationRow = (row) => ({
190
+ ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
178
191
  appVersion: row.app_version,
179
192
  channel: row.channel,
180
193
  cohort: row.metadata.cohort,
@@ -147,6 +147,8 @@ const assertEventRows = (rows, input) => {
147
147
  const toFailure = (failure) => ({
148
148
  stage: failure.stage,
149
149
  reason: failure.reason,
150
+ ...failure.error_message === void 0 ? {} : { errorMessage: failure.error_message },
151
+ ...failure.error_stack === void 0 ? {} : { errorStack: failure.error_stack },
150
152
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
151
153
  ...failure.http_status === void 0 ? {} : { httpStatus: failure.http_status },
152
154
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
@@ -155,12 +157,22 @@ const toFailure = (failure) => ({
155
157
  });
156
158
  /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
157
159
  const eventDetails = ({ type, metadata }) => ({
160
+ ...metadata.http_response !== void 0 ? { httpResponse: {
161
+ resource: metadata.http_response.resource,
162
+ path: metadata.http_response.path,
163
+ status: metadata.http_response.status,
164
+ body: metadata.http_response.body,
165
+ bodyTruncated: metadata.http_response.body_truncated,
166
+ receivedAtMs: metadata.http_response.received_at_ms
167
+ } } : {},
158
168
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
159
169
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
160
170
  ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
161
171
  ...type === "RECOVERED" && metadata.previous_process_exit !== void 0 ? { previousProcessExit: metadata.previous_process_exit } : {}
162
172
  });
163
173
  const toEventHistoryRow = (row) => ({
174
+ ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
175
+ ...row.metadata.sdk_version == null ? {} : { sdkVersion: row.metadata.sdk_version },
164
176
  appVersion: row.app_version,
165
177
  channel: row.channel,
166
178
  cohort: row.metadata.cohort,
@@ -175,6 +187,7 @@ const toEventHistoryRow = (row) => ({
175
187
  ...eventDetails(row)
176
188
  });
177
189
  const toInstallationRow = (row) => ({
190
+ ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
178
191
  appVersion: row.app_version,
179
192
  channel: row.channel,
180
193
  cohort: row.metadata.cohort,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/plugin-insights",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.27",
4
+ "version": "1.0.0-rc.29",
5
5
  "description": "Hot Updater's Insights plugin: launch, download, and update failure events, for the server and the React Native app",
6
6
  "exports": {
7
7
  "./client": {
@@ -33,10 +33,10 @@
33
33
  "package.json"
34
34
  ],
35
35
  "dependencies": {
36
- "@hot-updater/protocol": "1.0.0-rc.27"
36
+ "@hot-updater/protocol": "1.0.0-rc.28"
37
37
  },
38
38
  "peerDependencies": {
39
- "@hot-updater/plugin-core": "^1.0.0-rc.27"
39
+ "@hot-updater/plugin-core": "^1.0.0-rc.28"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@hot-updater/plugin-core": {
@@ -46,8 +46,8 @@
46
46
  "devDependencies": {
47
47
  "@types/node": "^20",
48
48
  "vitest": "4.1.4",
49
- "@hot-updater/plugin-core": "1.0.0-rc.27",
50
- "@hot-updater/test-utils": "1.0.0-rc.27"
49
+ "@hot-updater/plugin-core": "1.0.0-rc.28",
50
+ "@hot-updater/test-utils": "1.0.0-rc.28"
51
51
  },
52
52
  "nx": {
53
53
  "implicitDependencies": [