@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.
- package/dist/client/errorDetails.cjs +35 -0
- package/dist/client/errorDetails.mjs +34 -0
- package/dist/client/index.cjs +25 -4
- package/dist/client/index.mjs +25 -4
- package/dist/client/state.cjs +18 -0
- package/dist/client/state.mjs +18 -0
- package/dist/server/domain.d.cts +15 -1
- package/dist/server/domain.d.mts +15 -1
- package/dist/server/eventInput.cjs +38 -3
- package/dist/server/eventInput.mjs +38 -3
- package/dist/server/eventRow.cjs +3 -2
- package/dist/server/eventRow.d.cts +12 -1
- package/dist/server/eventRow.d.mts +12 -1
- package/dist/server/eventRow.mjs +3 -2
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.mts +2 -2
- package/dist/server/provider.cjs +13 -0
- package/dist/server/provider.mjs +13 -0
- package/package.json +5 -5
|
@@ -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 };
|
package/dist/client/index.cjs
CHANGED
|
@@ -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
|
|
149
|
+
const details = require_errorDetails.readErrorDetails(error.cause);
|
|
150
|
+
const failureKey = JSON.stringify([
|
|
144
151
|
error.stage,
|
|
145
152
|
error.reason,
|
|
146
|
-
targetBundleId
|
|
147
|
-
|
|
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) {
|
package/dist/client/index.mjs
CHANGED
|
@@ -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
|
|
148
|
+
const details = readErrorDetails(error.cause);
|
|
149
|
+
const failureKey = JSON.stringify([
|
|
143
150
|
error.stage,
|
|
144
151
|
error.reason,
|
|
145
|
-
targetBundleId
|
|
146
|
-
|
|
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) {
|
package/dist/client/state.cjs
CHANGED
|
@@ -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");
|
package/dist/client/state.mjs
CHANGED
|
@@ -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");
|
package/dist/server/domain.d.cts
CHANGED
|
@@ -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 };
|
package/dist/server/domain.d.mts
CHANGED
|
@@ -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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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 },
|
package/dist/server/eventRow.cjs
CHANGED
|
@@ -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 };
|
package/dist/server/eventRow.mjs
CHANGED
|
@@ -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";
|
package/dist/server/index.d.cts
CHANGED
|
@@ -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 };
|
package/dist/server/index.d.mts
CHANGED
|
@@ -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 };
|
package/dist/server/provider.cjs
CHANGED
|
@@ -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/dist/server/provider.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
36
|
+
"@hot-updater/protocol": "1.0.0-rc.28"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@hot-updater/plugin-core": "^1.0.0-rc.
|
|
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.
|
|
50
|
-
"@hot-updater/test-utils": "1.0.0-rc.
|
|
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": [
|