@logbrew/react-native 0.1.19 → 0.1.20
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/README.md +14 -1
- package/apple-native-diagnostics.d.ts +20 -0
- package/apple-native-diagnostics.js +97 -1
- package/index.cjs +1 -1
- package/index.js +36 -1207
- package/index.native.d.ts +1 -0
- package/index.native.js +6 -3
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +5 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +23 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +16 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +5 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +22 -10
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +16 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +77 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +11 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +6 -5
- package/package.json +1 -1
- package/src/NativeLogBrewAppleDiagnostics.ts +3 -0
package/README.md
CHANGED
|
@@ -309,7 +309,8 @@ before root registration, then start replay:
|
|
|
309
309
|
```js
|
|
310
310
|
import {
|
|
311
311
|
installLogBrewAppleNativeDiagnostics,
|
|
312
|
-
replayLogBrewAppleNativeDiagnostics
|
|
312
|
+
replayLogBrewAppleNativeDiagnostics,
|
|
313
|
+
setLogBrewAppleNativeCrashContext
|
|
313
314
|
} from "@logbrew/react-native/apple-native-diagnostics";
|
|
314
315
|
|
|
315
316
|
const nativeStatus = installLogBrewAppleNativeDiagnostics({
|
|
@@ -322,6 +323,13 @@ const nativeStatus = installLogBrewAppleNativeDiagnostics({
|
|
|
322
323
|
hangThresholdSeconds: 2
|
|
323
324
|
});
|
|
324
325
|
|
|
326
|
+
setLogBrewAppleNativeCrashContext({
|
|
327
|
+
schemaVersion: 1,
|
|
328
|
+
trace: { traceId: trace.traceId, spanId: trace.spanId, sampled: trace.sampled },
|
|
329
|
+
session: { id: "session_123" },
|
|
330
|
+
subject: { id: "subject_456", kind: "user" }
|
|
331
|
+
});
|
|
332
|
+
|
|
325
333
|
void replayLogBrewAppleNativeDiagnostics().catch((error) => {
|
|
326
334
|
console.warn(error?.code ?? "native_diagnostics_failed");
|
|
327
335
|
});
|
|
@@ -334,6 +342,11 @@ allowed, but only one integration may install native fatal capture in a given
|
|
|
334
342
|
process. LogBrew cannot transfer or remove that ownership before process
|
|
335
343
|
restart.
|
|
336
344
|
|
|
345
|
+
Update this snapshot when the active trace, session, or subject changes, and
|
|
346
|
+
clear it with `setLogBrewAppleNativeCrashContext(null)` on logout or session
|
|
347
|
+
end. The crash report keeps one atomic snapshot from the crashed process, so a
|
|
348
|
+
later app launch cannot replace it. Session and subject values must be opaque app-owned identifiers made from ASCII letters, numbers, `_`, or `-`. Do not use names, email addresses, IP addresses, or device identifiers. Resource context, tags, arbitrary fields, and values over the fixed 1 KiB snapshot limit fail before storage.
|
|
349
|
+
|
|
337
350
|
Installation creates app-private, data-protected storage that iOS excludes from
|
|
338
351
|
device data archives. Pending reports are partitioned by project, so changing
|
|
339
352
|
the configured project does not replay an earlier project's records with the
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TelemetrySessionContext, TelemetrySubjectContext, TelemetryTraceContext } from "@logbrew/sdk";
|
|
2
|
+
|
|
1
3
|
type LogBrewAppleNativeDiagnosticsAuth =
|
|
2
4
|
| { apiKey: string; clientKey?: never }
|
|
3
5
|
| { clientKey: string; apiKey?: never };
|
|
@@ -32,6 +34,19 @@ export type LogBrewAppleNativeDiagnosticsReplayResult = Readonly<{
|
|
|
32
34
|
pending: number;
|
|
33
35
|
}>;
|
|
34
36
|
|
|
37
|
+
export type LogBrewAppleNativeCrashContext = {
|
|
38
|
+
schemaVersion: 1;
|
|
39
|
+
trace?: TelemetryTraceContext;
|
|
40
|
+
session?: TelemetrySessionContext;
|
|
41
|
+
subject?: TelemetrySubjectContext;
|
|
42
|
+
resource?: never;
|
|
43
|
+
tags?: never;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type LogBrewAppleNativeCrashContextResult = Readonly<{
|
|
47
|
+
status: "cleared" | "updated";
|
|
48
|
+
}>;
|
|
49
|
+
|
|
35
50
|
export declare function installLogBrewAppleNativeDiagnostics(
|
|
36
51
|
configuration: LogBrewAppleNativeDiagnosticsConfiguration
|
|
37
52
|
): LogBrewAppleNativeDiagnosticsStatus;
|
|
@@ -43,10 +58,15 @@ export declare function replayLogBrewAppleNativeDiagnostics(): Promise<
|
|
|
43
58
|
export declare function getLogBrewAppleNativeDiagnosticsStatus():
|
|
44
59
|
LogBrewAppleNativeDiagnosticsStatus;
|
|
45
60
|
|
|
61
|
+
export declare function setLogBrewAppleNativeCrashContext(
|
|
62
|
+
context: LogBrewAppleNativeCrashContext | null
|
|
63
|
+
): LogBrewAppleNativeCrashContextResult;
|
|
64
|
+
|
|
46
65
|
declare const defaultExport: {
|
|
47
66
|
getLogBrewAppleNativeDiagnosticsStatus: typeof getLogBrewAppleNativeDiagnosticsStatus;
|
|
48
67
|
installLogBrewAppleNativeDiagnostics: typeof installLogBrewAppleNativeDiagnostics;
|
|
49
68
|
replayLogBrewAppleNativeDiagnostics: typeof replayLogBrewAppleNativeDiagnostics;
|
|
69
|
+
setLogBrewAppleNativeCrashContext: typeof setLogBrewAppleNativeCrashContext;
|
|
50
70
|
};
|
|
51
71
|
|
|
52
72
|
export default defaultExport;
|
|
@@ -44,6 +44,26 @@ export function getLogBrewAppleNativeDiagnosticsStatus() {
|
|
|
44
44
|
return requireStatusResult("status", result, new Set(["not_installed", "ready"]));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export function setLogBrewAppleNativeCrashContext(context) {
|
|
48
|
+
requireApplePlatform();
|
|
49
|
+
const nativeModule = requireNativeModule();
|
|
50
|
+
const payload = context === null ? null : normalizeCorrelationContext(context);
|
|
51
|
+
const result = callNative(nativeModule, "setNativeDiagnosticsContext", payload);
|
|
52
|
+
const expectedStatus = payload === null ? "cleared" : "updated";
|
|
53
|
+
if (isErrorResult(result)) {
|
|
54
|
+
throw new SdkError(result.code, `LogBrew Apple native diagnostics context failed with ${result.code}`);
|
|
55
|
+
}
|
|
56
|
+
if (!isPlainObject(result)
|
|
57
|
+
|| Object.keys(result).length !== 1
|
|
58
|
+
|| result.status !== expectedStatus) {
|
|
59
|
+
throw new SdkError(
|
|
60
|
+
"native_diagnostics_invalid_response",
|
|
61
|
+
"LogBrew Apple native diagnostics context returned an invalid response"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return Object.freeze({ status: expectedStatus });
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
function normalizeConfiguration(configuration) {
|
|
48
68
|
if (!configuration
|
|
49
69
|
|| Array.isArray(configuration)
|
|
@@ -109,6 +129,81 @@ function normalizeConfiguration(configuration) {
|
|
|
109
129
|
return payload;
|
|
110
130
|
}
|
|
111
131
|
|
|
132
|
+
function normalizeCorrelationContext(context) {
|
|
133
|
+
const source = exactObject(context, ["schemaVersion", "session", "subject", "trace"], "context");
|
|
134
|
+
if (source.schemaVersion !== 1) {
|
|
135
|
+
throw configurationError("context schemaVersion must be 1");
|
|
136
|
+
}
|
|
137
|
+
const output = { schemaVersion: 1 };
|
|
138
|
+
if (source.trace !== undefined) {
|
|
139
|
+
const trace = exactObject(
|
|
140
|
+
source.trace,
|
|
141
|
+
["parentSpanId", "sampled", "spanId", "traceId"],
|
|
142
|
+
"context trace"
|
|
143
|
+
);
|
|
144
|
+
output.trace = { traceId: correlationHexId(trace.traceId, 32, "traceId") };
|
|
145
|
+
for (const [key, width] of [["spanId", 16], ["parentSpanId", 16]]) {
|
|
146
|
+
if (trace[key] !== undefined) {
|
|
147
|
+
output.trace[key] = correlationHexId(trace[key], width, key);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (trace.sampled !== undefined) {
|
|
151
|
+
if (typeof trace.sampled !== "boolean") {
|
|
152
|
+
throw configurationError("context trace sampled must be a boolean");
|
|
153
|
+
}
|
|
154
|
+
output.trace.sampled = trace.sampled;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (source.session !== undefined) {
|
|
158
|
+
const session = exactObject(source.session, ["id", "previousId"], "context session");
|
|
159
|
+
output.session = { id: opaqueCorrelationId(session.id, "session id") };
|
|
160
|
+
if (session.previousId !== undefined) {
|
|
161
|
+
output.session.previousId = opaqueCorrelationId(session.previousId, "session previousId");
|
|
162
|
+
if (output.session.previousId === output.session.id) {
|
|
163
|
+
throw configurationError("context session previousId must differ from id");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (source.subject !== undefined) {
|
|
168
|
+
const subject = exactObject(source.subject, ["id", "kind"], "context subject");
|
|
169
|
+
if (subject.kind !== "anonymous" && subject.kind !== "user") {
|
|
170
|
+
throw configurationError("context subject kind must be anonymous or user");
|
|
171
|
+
}
|
|
172
|
+
output.subject = { id: opaqueCorrelationId(subject.id, "subject id"), kind: subject.kind };
|
|
173
|
+
}
|
|
174
|
+
if (Object.keys(output).length === 1) {
|
|
175
|
+
throw configurationError("context must include trace, session, or subject");
|
|
176
|
+
}
|
|
177
|
+
return output;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function exactObject(value, allowedKeys, name) {
|
|
181
|
+
if (!isPlainObject(value)) {
|
|
182
|
+
throw configurationError(`${name} must be an object`);
|
|
183
|
+
}
|
|
184
|
+
const unknown = Object.keys(value).filter((key) => !allowedKeys.includes(key)).sort();
|
|
185
|
+
if (unknown.length > 0) {
|
|
186
|
+
throw configurationError(`${name} contains unsupported fields`);
|
|
187
|
+
}
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function correlationHexId(value, width, name) {
|
|
192
|
+
if (typeof value !== "string"
|
|
193
|
+
|| !new RegExp(`^[0-9a-f]{${width}}$`, "iu").test(value)
|
|
194
|
+
|| /^0+$/u.test(value)) {
|
|
195
|
+
throw configurationError(`context trace ${name} must be a non-zero ${width}-character hex value`);
|
|
196
|
+
}
|
|
197
|
+
return value.toLowerCase();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function opaqueCorrelationId(value, name) {
|
|
201
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/u.test(value)) {
|
|
202
|
+
throw configurationError(`context ${name} must be an opaque app-owned identifier`);
|
|
203
|
+
}
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
|
|
112
207
|
function requireApplePlatform() {
|
|
113
208
|
if (Platform?.OS !== "ios") {
|
|
114
209
|
throw new SdkError(
|
|
@@ -280,7 +375,8 @@ function configurationError(message) {
|
|
|
280
375
|
const logBrewAppleNativeDiagnostics = {
|
|
281
376
|
getLogBrewAppleNativeDiagnosticsStatus,
|
|
282
377
|
installLogBrewAppleNativeDiagnostics,
|
|
283
|
-
replayLogBrewAppleNativeDiagnostics
|
|
378
|
+
replayLogBrewAppleNativeDiagnostics,
|
|
379
|
+
setLogBrewAppleNativeCrashContext
|
|
284
380
|
};
|
|
285
381
|
|
|
286
382
|
export default logBrewAppleNativeDiagnostics;
|
package/index.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
} = require("./metadata.cjs");
|
|
17
17
|
|
|
18
18
|
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
19
|
-
const DEFAULT_SDK_VERSION = "0.1.
|
|
19
|
+
const DEFAULT_SDK_VERSION = "0.1.20";
|
|
20
20
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
21
21
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
22
22
|
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|