@logbrew/react-native 0.1.0
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 +133 -0
- package/examples/index.mjs +36 -0
- package/examples/package.json +10 -0
- package/examples/readme-example.mjs +60 -0
- package/examples/real-user-smoke.mjs +145 -0
- package/index.cjs +473 -0
- package/index.d.cts +215 -0
- package/index.d.ts +215 -0
- package/index.js +456 -0
- package/index.native.js +50 -0
- package/package.json +59 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type {
|
|
3
|
+
ActionAttributes,
|
|
4
|
+
EnvironmentAttributes,
|
|
5
|
+
IssueAttributes,
|
|
6
|
+
LogAttributes,
|
|
7
|
+
LogBrewClient,
|
|
8
|
+
Metadata,
|
|
9
|
+
ReleaseAttributes,
|
|
10
|
+
SpanAttributes,
|
|
11
|
+
Transport,
|
|
12
|
+
TransportResponse
|
|
13
|
+
} from "@logbrew/sdk";
|
|
14
|
+
|
|
15
|
+
export type ReactNativePlatformLike = {
|
|
16
|
+
OS?: string;
|
|
17
|
+
Version?: string | number;
|
|
18
|
+
isPad?: boolean;
|
|
19
|
+
constants?: {
|
|
20
|
+
isTesting?: boolean;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ReactNativeAppStateLike = {
|
|
25
|
+
currentState?: string | null;
|
|
26
|
+
addEventListener?: (
|
|
27
|
+
type: "change",
|
|
28
|
+
listener: (state: string) => void
|
|
29
|
+
) => { remove(): void } | (() => void);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type CreateLogBrewReactNativeClientConfig = {
|
|
33
|
+
apiKey?: string;
|
|
34
|
+
clientKey?: string;
|
|
35
|
+
sdkName?: string;
|
|
36
|
+
sdkVersion?: string;
|
|
37
|
+
maxRetries?: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type TracePropagationTarget = string | RegExp | ((url: string) => boolean);
|
|
41
|
+
|
|
42
|
+
export type ReactNativeTraceparentConfig = {
|
|
43
|
+
traceId?: string;
|
|
44
|
+
spanId?: string;
|
|
45
|
+
traceFlags?: string;
|
|
46
|
+
randomValues?: (length: number) => ArrayLike<number>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type TraceparentFetchLike<TResponse = unknown> = (
|
|
50
|
+
input: any,
|
|
51
|
+
init?: any
|
|
52
|
+
) => Promise<TResponse> | TResponse;
|
|
53
|
+
|
|
54
|
+
export type TraceparentFetchConfig<TResponse = unknown> = {
|
|
55
|
+
fetchImpl?: TraceparentFetchLike<TResponse>;
|
|
56
|
+
randomValues?: (length: number) => ArrayLike<number>;
|
|
57
|
+
traceFlags?: string;
|
|
58
|
+
traceparent?: string;
|
|
59
|
+
traceparentFactory?: (context: {
|
|
60
|
+
input: any;
|
|
61
|
+
init?: any;
|
|
62
|
+
url: string;
|
|
63
|
+
}) => string | undefined;
|
|
64
|
+
tracePropagationTargets?: TracePropagationTarget[];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type ReactNativeContextOptions = {
|
|
68
|
+
platform?: ReactNativePlatformLike;
|
|
69
|
+
appState?: ReactNativeAppStateLike;
|
|
70
|
+
metadata?: Metadata;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type CaptureScreenViewOptions = ReactNativeContextOptions & {
|
|
74
|
+
id?: string;
|
|
75
|
+
timestamp?: string;
|
|
76
|
+
status?: ActionAttributes["status"];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type CaptureAppStateChangeOptions = ReactNativeContextOptions & {
|
|
80
|
+
id?: string;
|
|
81
|
+
timestamp?: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type ReactNativeErrorEvent = {
|
|
85
|
+
id: string;
|
|
86
|
+
timestamp: string;
|
|
87
|
+
attributes: IssueAttributes;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type ReactNativeErrorIdFactoryContext = {
|
|
91
|
+
error: unknown;
|
|
92
|
+
message: string;
|
|
93
|
+
screen?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
97
|
+
id?: string;
|
|
98
|
+
idFactory?: (context: ReactNativeErrorIdFactoryContext) => string;
|
|
99
|
+
includeStack?: boolean;
|
|
100
|
+
level?: IssueAttributes["level"];
|
|
101
|
+
now?: () => string;
|
|
102
|
+
screen?: string;
|
|
103
|
+
timestamp?: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type LogBrewNativeProviderProps = {
|
|
107
|
+
client: LogBrewClient;
|
|
108
|
+
platform?: ReactNativePlatformLike;
|
|
109
|
+
appState?: ReactNativeAppStateLike;
|
|
110
|
+
children?: React.ReactNode;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type LogBrewNativeContextValue = {
|
|
114
|
+
client: LogBrewClient;
|
|
115
|
+
platform?: ReactNativePlatformLike;
|
|
116
|
+
appState?: ReactNativeAppStateLike;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type LogBrewNativeActions = {
|
|
120
|
+
release(id: string, timestamp: string, attributes: ReleaseAttributes): void;
|
|
121
|
+
environment(id: string, timestamp: string, attributes: EnvironmentAttributes): void;
|
|
122
|
+
issue(id: string, timestamp: string, attributes: IssueAttributes): void;
|
|
123
|
+
log(id: string, timestamp: string, attributes: LogAttributes): void;
|
|
124
|
+
span(id: string, timestamp: string, attributes: SpanAttributes): void;
|
|
125
|
+
action(id: string, timestamp: string, attributes: ActionAttributes): void;
|
|
126
|
+
flush(transport: Transport): Promise<TransportResponse>;
|
|
127
|
+
shutdown(transport: Transport): Promise<TransportResponse>;
|
|
128
|
+
previewJson(): string;
|
|
129
|
+
pendingEvents(): number;
|
|
130
|
+
captureScreenView(screenName: string, options?: CaptureScreenViewOptions): void;
|
|
131
|
+
captureAppStateChange(state: string, options?: CaptureAppStateChangeOptions): void;
|
|
132
|
+
captureReactNativeError(error: unknown, options?: CaptureReactNativeErrorOptions): ReactNativeErrorEvent;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export declare function createLogBrewReactNativeClient(
|
|
136
|
+
config: CreateLogBrewReactNativeClientConfig
|
|
137
|
+
): LogBrewClient;
|
|
138
|
+
export declare function createReactNativeTraceparent(config?: ReactNativeTraceparentConfig): string;
|
|
139
|
+
export declare function createTraceparentFetch<TResponse = unknown>(
|
|
140
|
+
config?: TraceparentFetchConfig<TResponse>
|
|
141
|
+
): TraceparentFetchLike<TResponse>;
|
|
142
|
+
export declare function shouldPropagateTraceparent(
|
|
143
|
+
url: string,
|
|
144
|
+
tracePropagationTargets?: TracePropagationTarget[]
|
|
145
|
+
): boolean;
|
|
146
|
+
export declare function getReactNativeContext(options?: ReactNativeContextOptions): Metadata;
|
|
147
|
+
export declare function captureScreenView(
|
|
148
|
+
client: LogBrewClient,
|
|
149
|
+
screenName: string,
|
|
150
|
+
options?: CaptureScreenViewOptions
|
|
151
|
+
): void;
|
|
152
|
+
export declare function captureAppStateChange(
|
|
153
|
+
client: LogBrewClient,
|
|
154
|
+
state: string,
|
|
155
|
+
options?: CaptureAppStateChangeOptions
|
|
156
|
+
): void;
|
|
157
|
+
export declare function createReactNativeErrorEvent(
|
|
158
|
+
error: unknown,
|
|
159
|
+
options?: CaptureReactNativeErrorOptions
|
|
160
|
+
): ReactNativeErrorEvent;
|
|
161
|
+
export declare function captureReactNativeError(
|
|
162
|
+
client: LogBrewClient,
|
|
163
|
+
error: unknown,
|
|
164
|
+
options?: CaptureReactNativeErrorOptions
|
|
165
|
+
): ReactNativeErrorEvent;
|
|
166
|
+
export declare function createAppStateListener(
|
|
167
|
+
client: LogBrewClient,
|
|
168
|
+
appState: ReactNativeAppStateLike,
|
|
169
|
+
options?: CaptureAppStateChangeOptions
|
|
170
|
+
): () => void;
|
|
171
|
+
export declare function LogBrewNativeProvider(props: LogBrewNativeProviderProps): React.ReactElement;
|
|
172
|
+
export declare function useLogBrewNative(): LogBrewNativeContextValue;
|
|
173
|
+
export declare function useLogBrewNativeActions(): LogBrewNativeActions;
|
|
174
|
+
|
|
175
|
+
export declare function createDefaultLogBrewReactNativeClient(
|
|
176
|
+
config: CreateLogBrewReactNativeClientConfig
|
|
177
|
+
): LogBrewClient;
|
|
178
|
+
export declare function getDefaultReactNativeContext(options?: { metadata?: Metadata }): Metadata;
|
|
179
|
+
export declare function captureDefaultScreenView(
|
|
180
|
+
client: LogBrewClient,
|
|
181
|
+
screenName: string,
|
|
182
|
+
options?: Omit<CaptureScreenViewOptions, "platform" | "appState">
|
|
183
|
+
): void;
|
|
184
|
+
export declare function captureDefaultAppStateChange(
|
|
185
|
+
client: LogBrewClient,
|
|
186
|
+
state: string,
|
|
187
|
+
options?: Omit<CaptureAppStateChangeOptions, "platform" | "appState">
|
|
188
|
+
): void;
|
|
189
|
+
export declare function captureDefaultReactNativeError(
|
|
190
|
+
client: LogBrewClient,
|
|
191
|
+
error: unknown,
|
|
192
|
+
options?: Omit<CaptureReactNativeErrorOptions, "platform" | "appState">
|
|
193
|
+
): ReactNativeErrorEvent;
|
|
194
|
+
export declare function createDefaultAppStateListener(
|
|
195
|
+
client: LogBrewClient,
|
|
196
|
+
options?: Omit<CaptureAppStateChangeOptions, "appState">
|
|
197
|
+
): () => void;
|
|
198
|
+
|
|
199
|
+
declare const defaultExport: {
|
|
200
|
+
LogBrewNativeProvider: typeof LogBrewNativeProvider;
|
|
201
|
+
captureAppStateChange: typeof captureAppStateChange;
|
|
202
|
+
captureReactNativeError: typeof captureReactNativeError;
|
|
203
|
+
captureScreenView: typeof captureScreenView;
|
|
204
|
+
createAppStateListener: typeof createAppStateListener;
|
|
205
|
+
createLogBrewReactNativeClient: typeof createLogBrewReactNativeClient;
|
|
206
|
+
createReactNativeErrorEvent: typeof createReactNativeErrorEvent;
|
|
207
|
+
createReactNativeTraceparent: typeof createReactNativeTraceparent;
|
|
208
|
+
createTraceparentFetch: typeof createTraceparentFetch;
|
|
209
|
+
getReactNativeContext: typeof getReactNativeContext;
|
|
210
|
+
shouldPropagateTraceparent: typeof shouldPropagateTraceparent;
|
|
211
|
+
useLogBrewNative: typeof useLogBrewNative;
|
|
212
|
+
useLogBrewNativeActions: typeof useLogBrewNativeActions;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export default defaultExport;
|
package/index.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
createTraceparent,
|
|
4
|
+
LogBrewClient,
|
|
5
|
+
parseTraceparent,
|
|
6
|
+
SdkError
|
|
7
|
+
} from "@logbrew/sdk";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
10
|
+
const DEFAULT_SDK_VERSION = "0.1.0";
|
|
11
|
+
const LogBrewNativeContext = React.createContext(null);
|
|
12
|
+
|
|
13
|
+
export function createLogBrewReactNativeClient({
|
|
14
|
+
apiKey,
|
|
15
|
+
clientKey,
|
|
16
|
+
sdkName = DEFAULT_SDK_NAME,
|
|
17
|
+
sdkVersion = DEFAULT_SDK_VERSION,
|
|
18
|
+
maxRetries = 2
|
|
19
|
+
}) {
|
|
20
|
+
const authKey = clientKey ?? apiKey;
|
|
21
|
+
if (!authKey) {
|
|
22
|
+
throw new SdkError("configuration_error", "createLogBrewReactNativeClient requires clientKey or apiKey");
|
|
23
|
+
}
|
|
24
|
+
return LogBrewClient.create({ apiKey: authKey, sdkName, sdkVersion, maxRetries });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createReactNativeTraceparent({
|
|
28
|
+
randomValues = defaultRandomValues,
|
|
29
|
+
spanId,
|
|
30
|
+
traceFlags = "01",
|
|
31
|
+
traceId
|
|
32
|
+
} = {}) {
|
|
33
|
+
return createTraceparent({
|
|
34
|
+
spanId: spanId ?? randomHex(8, randomValues),
|
|
35
|
+
traceFlags,
|
|
36
|
+
traceId: traceId ?? randomHex(16, randomValues)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createTraceparentFetch({
|
|
41
|
+
fetchImpl = defaultFetch(),
|
|
42
|
+
randomValues = defaultRandomValues,
|
|
43
|
+
traceFlags = "01",
|
|
44
|
+
traceparent,
|
|
45
|
+
traceparentFactory,
|
|
46
|
+
tracePropagationTargets = []
|
|
47
|
+
} = {}) {
|
|
48
|
+
if (typeof fetchImpl !== "function") {
|
|
49
|
+
throw new SdkError("configuration_error", "createTraceparentFetch requires fetch");
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(tracePropagationTargets)) {
|
|
52
|
+
throw new SdkError("configuration_error", "tracePropagationTargets must be an array");
|
|
53
|
+
}
|
|
54
|
+
if (traceparentFactory !== undefined && typeof traceparentFactory !== "function") {
|
|
55
|
+
throw new SdkError("configuration_error", "traceparentFactory must be a function");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return async function tracedFetch(input, init) {
|
|
59
|
+
const url = requestUrl(input);
|
|
60
|
+
if (!shouldPropagateTraceparent(url, tracePropagationTargets)) {
|
|
61
|
+
return init === undefined ? fetchImpl(input) : fetchImpl(input, init);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const requestInit = init ?? {};
|
|
65
|
+
const nextTraceparent = traceparentForRequest({
|
|
66
|
+
init,
|
|
67
|
+
input,
|
|
68
|
+
randomValues,
|
|
69
|
+
traceFlags,
|
|
70
|
+
traceparent,
|
|
71
|
+
traceparentFactory,
|
|
72
|
+
url
|
|
73
|
+
});
|
|
74
|
+
const nextInit = {
|
|
75
|
+
...requestInit,
|
|
76
|
+
headers: headersWithTraceparent(requestHeaders(input, requestInit), nextTraceparent)
|
|
77
|
+
};
|
|
78
|
+
return fetchImpl(input, nextInit);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function shouldPropagateTraceparent(url, tracePropagationTargets = []) {
|
|
83
|
+
if (!Array.isArray(tracePropagationTargets)) {
|
|
84
|
+
throw new SdkError("configuration_error", "tracePropagationTargets must be an array");
|
|
85
|
+
}
|
|
86
|
+
return tracePropagationTargets.some((target) => {
|
|
87
|
+
if (typeof target === "string") {
|
|
88
|
+
return url.includes(target);
|
|
89
|
+
}
|
|
90
|
+
if (target instanceof RegExp) {
|
|
91
|
+
target.lastIndex = 0;
|
|
92
|
+
const matched = target.test(url);
|
|
93
|
+
target.lastIndex = 0;
|
|
94
|
+
return matched;
|
|
95
|
+
}
|
|
96
|
+
if (typeof target === "function") {
|
|
97
|
+
return target(url) === true;
|
|
98
|
+
}
|
|
99
|
+
throw new SdkError("configuration_error", "tracePropagationTargets entries must be strings, RegExp values, or functions");
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function getReactNativeContext({ platform, appState, metadata = {} } = {}) {
|
|
104
|
+
return {
|
|
105
|
+
platform: platform?.OS ?? "unknown",
|
|
106
|
+
platformVersion: normalizeMetadataValue(platform?.Version),
|
|
107
|
+
appState: normalizeMetadataValue(appState?.currentState),
|
|
108
|
+
isPad: normalizeMetadataValue(platform?.isPad),
|
|
109
|
+
isTesting: normalizeMetadataValue(platform?.constants?.isTesting),
|
|
110
|
+
...metadata
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function captureScreenView(client, screenName, {
|
|
115
|
+
id = `evt_screen_${slugify(screenName)}`,
|
|
116
|
+
timestamp = new Date().toISOString(),
|
|
117
|
+
status = "success",
|
|
118
|
+
platform,
|
|
119
|
+
appState,
|
|
120
|
+
metadata = {}
|
|
121
|
+
} = {}) {
|
|
122
|
+
requireClient(client);
|
|
123
|
+
requireNonEmpty("screen name", screenName);
|
|
124
|
+
client.action(id, timestamp, {
|
|
125
|
+
name: `screen:${screenName}`,
|
|
126
|
+
status,
|
|
127
|
+
metadata: {
|
|
128
|
+
...getReactNativeContext({ platform, appState }),
|
|
129
|
+
screen: screenName,
|
|
130
|
+
...metadata
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function captureAppStateChange(client, state, {
|
|
136
|
+
id = `evt_app_state_${slugify(state)}`,
|
|
137
|
+
timestamp = new Date().toISOString(),
|
|
138
|
+
platform,
|
|
139
|
+
appState,
|
|
140
|
+
metadata = {}
|
|
141
|
+
} = {}) {
|
|
142
|
+
requireClient(client);
|
|
143
|
+
requireNonEmpty("app state", state);
|
|
144
|
+
client.action(id, timestamp, {
|
|
145
|
+
name: "app_state_change",
|
|
146
|
+
status: "success",
|
|
147
|
+
metadata: {
|
|
148
|
+
...getReactNativeContext({ platform, appState }),
|
|
149
|
+
appState: state,
|
|
150
|
+
...metadata
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function createReactNativeErrorEvent(error, {
|
|
156
|
+
id,
|
|
157
|
+
idFactory = defaultErrorEventId,
|
|
158
|
+
includeStack = false,
|
|
159
|
+
level = "error",
|
|
160
|
+
metadata = {},
|
|
161
|
+
now = () => new Date().toISOString(),
|
|
162
|
+
platform,
|
|
163
|
+
appState,
|
|
164
|
+
screen,
|
|
165
|
+
timestamp
|
|
166
|
+
} = {}) {
|
|
167
|
+
const details = errorDetails(error, includeStack);
|
|
168
|
+
const eventMetadata = compactMetadata({
|
|
169
|
+
...getReactNativeContext({ platform, appState }),
|
|
170
|
+
errorName: details.name,
|
|
171
|
+
errorValueType: details.valueType,
|
|
172
|
+
source: "react-native.error",
|
|
173
|
+
screen,
|
|
174
|
+
...(includeStack ? { errorStack: details.stack } : {}),
|
|
175
|
+
...metadata
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
id: id ?? idFactory({ error, message: details.message, screen }),
|
|
179
|
+
timestamp: timestamp ?? now(),
|
|
180
|
+
attributes: {
|
|
181
|
+
title: `React Native error: ${details.message}`,
|
|
182
|
+
level,
|
|
183
|
+
message: details.message,
|
|
184
|
+
metadata: eventMetadata
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function captureReactNativeError(client, error, options = {}) {
|
|
190
|
+
requireClient(client);
|
|
191
|
+
const event = createReactNativeErrorEvent(error, options);
|
|
192
|
+
client.issue(event.id, event.timestamp, event.attributes);
|
|
193
|
+
return event;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createAppStateListener(client, appState, options = {}) {
|
|
197
|
+
requireClient(client);
|
|
198
|
+
if (!appState || typeof appState.addEventListener !== "function") {
|
|
199
|
+
throw new SdkError("configuration_error", "createAppStateListener requires AppState.addEventListener");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const subscription = appState.addEventListener("change", (nextState) => {
|
|
203
|
+
captureAppStateChange(client, nextState, {
|
|
204
|
+
...options,
|
|
205
|
+
appState
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (typeof subscription === "function") {
|
|
210
|
+
return subscription;
|
|
211
|
+
}
|
|
212
|
+
if (subscription && typeof subscription.remove === "function") {
|
|
213
|
+
return () => subscription.remove();
|
|
214
|
+
}
|
|
215
|
+
return () => {};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function LogBrewNativeProvider({ client, platform, appState, children }) {
|
|
219
|
+
requireClient(client);
|
|
220
|
+
const value = React.useMemo(() => ({
|
|
221
|
+
client,
|
|
222
|
+
platform,
|
|
223
|
+
appState
|
|
224
|
+
}), [appState, client, platform]);
|
|
225
|
+
return React.createElement(LogBrewNativeContext.Provider, { value }, children);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function useLogBrewNative() {
|
|
229
|
+
const value = React.useContext(LogBrewNativeContext);
|
|
230
|
+
if (!value?.client) {
|
|
231
|
+
throw new SdkError("configuration_error", "useLogBrewNative must be used inside LogBrewNativeProvider");
|
|
232
|
+
}
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function useLogBrewNativeActions() {
|
|
237
|
+
const { client, platform, appState } = useLogBrewNative();
|
|
238
|
+
return {
|
|
239
|
+
release: client.release.bind(client),
|
|
240
|
+
environment: client.environment.bind(client),
|
|
241
|
+
issue: client.issue.bind(client),
|
|
242
|
+
log: client.log.bind(client),
|
|
243
|
+
span: client.span.bind(client),
|
|
244
|
+
action: client.action.bind(client),
|
|
245
|
+
flush: client.flush.bind(client),
|
|
246
|
+
shutdown: client.shutdown.bind(client),
|
|
247
|
+
previewJson: client.previewJson.bind(client),
|
|
248
|
+
pendingEvents: client.pendingEvents.bind(client),
|
|
249
|
+
captureScreenView: (screenName, options = {}) => captureScreenView(client, screenName, {
|
|
250
|
+
platform,
|
|
251
|
+
appState,
|
|
252
|
+
...options
|
|
253
|
+
}),
|
|
254
|
+
captureAppStateChange: (state, options = {}) => captureAppStateChange(client, state, {
|
|
255
|
+
platform,
|
|
256
|
+
appState,
|
|
257
|
+
...options
|
|
258
|
+
}),
|
|
259
|
+
captureReactNativeError: (error, options = {}) => captureReactNativeError(client, error, {
|
|
260
|
+
platform,
|
|
261
|
+
appState,
|
|
262
|
+
...options
|
|
263
|
+
})
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function requireClient(client) {
|
|
268
|
+
if (!client) {
|
|
269
|
+
throw new SdkError("configuration_error", "LogBrew React Native helpers require a client");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function requireNonEmpty(label, value) {
|
|
274
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
275
|
+
throw new SdkError("validation_error", `${label} must be non-empty`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizeMetadataValue(value) {
|
|
280
|
+
if (value === undefined) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
284
|
+
return value;
|
|
285
|
+
}
|
|
286
|
+
return String(value);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function compactMetadata(metadata) {
|
|
290
|
+
const compacted = {};
|
|
291
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
292
|
+
if (value === undefined) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
296
|
+
compacted[key] = value;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return compacted;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function errorDetails(error, includeStack) {
|
|
303
|
+
const candidate = error?.reason ?? error?.error ?? error;
|
|
304
|
+
const message = errorMessage(candidate);
|
|
305
|
+
return {
|
|
306
|
+
message,
|
|
307
|
+
name: errorName(candidate),
|
|
308
|
+
stack: includeStack && typeof candidate?.stack === "string" ? candidate.stack : undefined,
|
|
309
|
+
valueType: candidate === null ? "null" : typeof candidate
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function errorName(error) {
|
|
314
|
+
if (error instanceof Error && typeof error.name === "string" && error.name.trim() !== "") {
|
|
315
|
+
return error.name;
|
|
316
|
+
}
|
|
317
|
+
if (typeof error?.name === "string" && error.name.trim() !== "") {
|
|
318
|
+
return error.name;
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function errorMessage(error) {
|
|
324
|
+
if (error instanceof Error && typeof error.message === "string" && error.message.trim() !== "") {
|
|
325
|
+
return error.message;
|
|
326
|
+
}
|
|
327
|
+
if (typeof error?.message === "string" && error.message.trim() !== "") {
|
|
328
|
+
return error.message;
|
|
329
|
+
}
|
|
330
|
+
if (typeof error === "string" && error.trim() !== "") {
|
|
331
|
+
return error;
|
|
332
|
+
}
|
|
333
|
+
return String(error ?? "unknown error");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function defaultErrorEventId({ message, screen }) {
|
|
337
|
+
return `evt_native_error_${slugify(`${screen ?? "app"}_${message}`)}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function defaultFetch() {
|
|
341
|
+
return typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function defaultRandomValues(length) {
|
|
345
|
+
if (!globalThis.crypto || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
346
|
+
throw new SdkError("configuration_error", "createReactNativeTraceparent requires crypto.getRandomValues or randomValues");
|
|
347
|
+
}
|
|
348
|
+
const bytes = new Uint8Array(length);
|
|
349
|
+
return globalThis.crypto.getRandomValues(bytes);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function headersWithTraceparent(headers, traceparent) {
|
|
353
|
+
const nextHeaders = {};
|
|
354
|
+
for (const [key, value] of headerEntries(headers)) {
|
|
355
|
+
if (String(key).toLowerCase() !== "traceparent") {
|
|
356
|
+
nextHeaders[key] = value;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
nextHeaders.traceparent = traceparent;
|
|
360
|
+
return nextHeaders;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function headerEntries(headers) {
|
|
364
|
+
if (headers === undefined || headers === null) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
const HeadersConstructor = globalThis.Headers;
|
|
368
|
+
if (typeof HeadersConstructor === "function" && headers instanceof HeadersConstructor) {
|
|
369
|
+
const entries = [];
|
|
370
|
+
headers.forEach((value, key) => {
|
|
371
|
+
entries.push([key, value]);
|
|
372
|
+
});
|
|
373
|
+
return entries;
|
|
374
|
+
}
|
|
375
|
+
if (Array.isArray(headers)) {
|
|
376
|
+
return headers;
|
|
377
|
+
}
|
|
378
|
+
if (typeof headers !== "string" && typeof headers[Symbol.iterator] === "function") {
|
|
379
|
+
return Array.from(headers);
|
|
380
|
+
}
|
|
381
|
+
if (typeof headers === "object") {
|
|
382
|
+
return Object.entries(headers);
|
|
383
|
+
}
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function randomHex(length, randomValues) {
|
|
388
|
+
if (typeof randomValues !== "function") {
|
|
389
|
+
throw new SdkError("configuration_error", "randomValues must be a function");
|
|
390
|
+
}
|
|
391
|
+
const bytes = Array.from(randomValues(length));
|
|
392
|
+
if (bytes.length !== length || bytes.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
|
|
393
|
+
throw new SdkError("configuration_error", "randomValues must return byte values for the requested length");
|
|
394
|
+
}
|
|
395
|
+
return bytes.map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function requestHeaders(input, init) {
|
|
399
|
+
if (init && init.headers !== undefined) {
|
|
400
|
+
return init.headers;
|
|
401
|
+
}
|
|
402
|
+
return input?.headers;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function requestUrl(input) {
|
|
406
|
+
if (typeof input === "string") {
|
|
407
|
+
return input;
|
|
408
|
+
}
|
|
409
|
+
const URLConstructor = globalThis.URL;
|
|
410
|
+
if (typeof URLConstructor === "function" && input instanceof URLConstructor) {
|
|
411
|
+
return input.toString();
|
|
412
|
+
}
|
|
413
|
+
if (typeof input?.url === "string") {
|
|
414
|
+
return input.url;
|
|
415
|
+
}
|
|
416
|
+
return String(input);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function slugify(value) {
|
|
420
|
+
return value
|
|
421
|
+
.toLowerCase()
|
|
422
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
423
|
+
.replace(/^_+|_+$/g, "") || "event";
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function traceparentForRequest({
|
|
427
|
+
init,
|
|
428
|
+
input,
|
|
429
|
+
randomValues,
|
|
430
|
+
traceFlags,
|
|
431
|
+
traceparent,
|
|
432
|
+
traceparentFactory,
|
|
433
|
+
url
|
|
434
|
+
}) {
|
|
435
|
+
const nextTraceparent = typeof traceparentFactory === "function"
|
|
436
|
+
? traceparentFactory({ init, input, url })
|
|
437
|
+
: traceparent ?? createReactNativeTraceparent({ randomValues, traceFlags });
|
|
438
|
+
parseTraceparent(nextTraceparent);
|
|
439
|
+
return nextTraceparent;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export default {
|
|
443
|
+
LogBrewNativeProvider,
|
|
444
|
+
captureAppStateChange,
|
|
445
|
+
captureReactNativeError,
|
|
446
|
+
captureScreenView,
|
|
447
|
+
createAppStateListener,
|
|
448
|
+
createLogBrewReactNativeClient,
|
|
449
|
+
createReactNativeErrorEvent,
|
|
450
|
+
createReactNativeTraceparent,
|
|
451
|
+
createTraceparentFetch,
|
|
452
|
+
getReactNativeContext,
|
|
453
|
+
shouldPropagateTraceparent,
|
|
454
|
+
useLogBrewNative,
|
|
455
|
+
useLogBrewNativeActions
|
|
456
|
+
};
|
package/index.native.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AppState, Platform } from "react-native";
|
|
2
|
+
import {
|
|
3
|
+
captureAppStateChange,
|
|
4
|
+
captureReactNativeError,
|
|
5
|
+
captureScreenView,
|
|
6
|
+
createAppStateListener,
|
|
7
|
+
createLogBrewReactNativeClient,
|
|
8
|
+
getReactNativeContext
|
|
9
|
+
} from "./index.js";
|
|
10
|
+
|
|
11
|
+
export * from "./index.js";
|
|
12
|
+
|
|
13
|
+
export function createDefaultLogBrewReactNativeClient(config = {}) {
|
|
14
|
+
return createLogBrewReactNativeClient(config);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getDefaultReactNativeContext({ metadata = {} } = {}) {
|
|
18
|
+
return getReactNativeContext({ platform: Platform, appState: AppState, metadata });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function captureDefaultScreenView(client, screenName, options = {}) {
|
|
22
|
+
return captureScreenView(client, screenName, {
|
|
23
|
+
platform: Platform,
|
|
24
|
+
appState: AppState,
|
|
25
|
+
...options
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function captureDefaultAppStateChange(client, state, options = {}) {
|
|
30
|
+
return captureAppStateChange(client, state, {
|
|
31
|
+
platform: Platform,
|
|
32
|
+
appState: AppState,
|
|
33
|
+
...options
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function captureDefaultReactNativeError(client, error, options = {}) {
|
|
38
|
+
return captureReactNativeError(client, error, {
|
|
39
|
+
platform: Platform,
|
|
40
|
+
appState: AppState,
|
|
41
|
+
...options
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createDefaultAppStateListener(client, options = {}) {
|
|
46
|
+
return createAppStateListener(client, AppState, {
|
|
47
|
+
platform: Platform,
|
|
48
|
+
...options
|
|
49
|
+
});
|
|
50
|
+
}
|