@luciq/react-native 19.2.1 → 19.3.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/CHANGELOG.md +30 -0
- package/README.md +87 -0
- package/RNLuciq.podspec +1 -1
- package/android/native.gradle +1 -1
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqAPMModule.java +202 -117
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +20 -0
- package/dist/constants/Strings.d.ts +9 -0
- package/dist/constants/Strings.js +12 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/models/CustomSpan.d.ts +47 -0
- package/dist/models/CustomSpan.js +82 -0
- package/dist/modules/APM.d.ts +58 -0
- package/dist/modules/APM.js +62 -0
- package/dist/native/NativeAPM.d.ts +3 -0
- package/dist/native/NativeLuciq.d.ts +1 -0
- package/dist/utils/CustomSpansManager.d.ts +38 -0
- package/dist/utils/CustomSpansManager.js +173 -0
- package/ios/RNLuciq/LuciqAPMBridge.h +13 -0
- package/ios/RNLuciq/LuciqAPMBridge.m +55 -0
- package/ios/RNLuciq/LuciqReactBridge.m +12 -0
- package/ios/RNLuciq/Util/LCQAPM+PrivateAPIs.h +1 -0
- package/ios/native.rb +1 -1
- package/package.json +1 -1
- package/plugin/build/index.js +9 -2
- package/plugin/src/withLuciqIOS.ts +9 -2
- package/scripts/releases/changelog_to_slack_formatter.sh +9 -0
- package/scripts/releases/get_job_approver.sh +60 -0
- package/scripts/releases/get_release_notes.sh +22 -0
- package/scripts/releases/get_sdk_version.sh +5 -0
- package/scripts/releases/get_slack_id_from_username.sh +24 -0
- package/src/constants/Strings.ts +24 -0
- package/src/index.ts +2 -0
- package/src/models/CustomSpan.ts +102 -0
- package/src/modules/APM.ts +72 -0
- package/src/native/NativeAPM.ts +7 -0
- package/src/native/NativeLuciq.ts +1 -0
- package/src/utils/CustomSpansManager.ts +202 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback to unregister a span from tracking
|
|
3
|
+
*/
|
|
4
|
+
type UnregisterCallback = (span: CustomSpan) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Callback to sync span data to native SDK
|
|
7
|
+
*/
|
|
8
|
+
type SyncCallback = (name: string, startTimestamp: number, endTimestamp: number) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Represents a custom span for performance tracking.
|
|
11
|
+
* A span measures the duration of an operation and reports it to the native SDK.
|
|
12
|
+
*/
|
|
13
|
+
export declare class CustomSpan {
|
|
14
|
+
private name;
|
|
15
|
+
private startTime;
|
|
16
|
+
private startMonotonic;
|
|
17
|
+
private endTime?;
|
|
18
|
+
private duration?;
|
|
19
|
+
private hasEnded;
|
|
20
|
+
private endPromise?;
|
|
21
|
+
private unregisterCallback;
|
|
22
|
+
private syncCallback;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new custom span. The span starts immediately upon creation.
|
|
25
|
+
* @internal - Use APM.startCustomSpan() instead
|
|
26
|
+
*/
|
|
27
|
+
constructor(name: string, unregisterCallback: UnregisterCallback, syncCallback: SyncCallback);
|
|
28
|
+
/**
|
|
29
|
+
* Ends this custom span and reports it to the native SDK.
|
|
30
|
+
* This method is idempotent - calling it multiple times is safe.
|
|
31
|
+
* Subsequent calls will wait for the first call to complete.
|
|
32
|
+
*/
|
|
33
|
+
end(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Get the span name
|
|
36
|
+
*/
|
|
37
|
+
getName(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Check if the span has ended
|
|
40
|
+
*/
|
|
41
|
+
isEnded(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Get the span duration in milliseconds (only available after end())
|
|
44
|
+
*/
|
|
45
|
+
getDuration(): number | undefined;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a custom span for performance tracking.
|
|
3
|
+
* A span measures the duration of an operation and reports it to the native SDK.
|
|
4
|
+
*/
|
|
5
|
+
export class CustomSpan {
|
|
6
|
+
name;
|
|
7
|
+
startTime; // Date.now() in milliseconds
|
|
8
|
+
startMonotonic; // performance.now() in milliseconds
|
|
9
|
+
endTime;
|
|
10
|
+
duration;
|
|
11
|
+
hasEnded = false;
|
|
12
|
+
endPromise;
|
|
13
|
+
unregisterCallback;
|
|
14
|
+
syncCallback;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new custom span. The span starts immediately upon creation.
|
|
17
|
+
* @internal - Use APM.startCustomSpan() instead
|
|
18
|
+
*/
|
|
19
|
+
constructor(name, unregisterCallback, syncCallback) {
|
|
20
|
+
this.name = name;
|
|
21
|
+
this.startTime = Date.now();
|
|
22
|
+
this.startMonotonic = performance.now();
|
|
23
|
+
this.unregisterCallback = unregisterCallback;
|
|
24
|
+
this.syncCallback = syncCallback;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Ends this custom span and reports it to the native SDK.
|
|
28
|
+
* This method is idempotent - calling it multiple times is safe.
|
|
29
|
+
* Subsequent calls will wait for the first call to complete.
|
|
30
|
+
*/
|
|
31
|
+
async end() {
|
|
32
|
+
// Thread-safe check using Promise-based locking
|
|
33
|
+
if (this.hasEnded) {
|
|
34
|
+
if (this.endPromise) {
|
|
35
|
+
await this.endPromise;
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Create lock and mark as ended
|
|
40
|
+
let resolveEnd;
|
|
41
|
+
this.endPromise = new Promise((resolve) => {
|
|
42
|
+
resolveEnd = resolve;
|
|
43
|
+
});
|
|
44
|
+
this.hasEnded = true;
|
|
45
|
+
try {
|
|
46
|
+
// Unregister from active spans
|
|
47
|
+
this.unregisterCallback(this);
|
|
48
|
+
// Calculate duration using monotonic clock
|
|
49
|
+
const endMonotonic = performance.now();
|
|
50
|
+
this.duration = endMonotonic - this.startMonotonic;
|
|
51
|
+
// Calculate end time using wall clock
|
|
52
|
+
this.endTime = this.startTime + this.duration;
|
|
53
|
+
// Convert to microseconds for native SDK
|
|
54
|
+
const startMicros = this.startTime * 1000;
|
|
55
|
+
const endMicros = this.endTime * 1000;
|
|
56
|
+
// Send to native SDK
|
|
57
|
+
await this.syncCallback(this.name, startMicros, endMicros);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
// Release lock
|
|
61
|
+
resolveEnd();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the span name
|
|
66
|
+
*/
|
|
67
|
+
getName() {
|
|
68
|
+
return this.name;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if the span has ended
|
|
72
|
+
*/
|
|
73
|
+
isEnded() {
|
|
74
|
+
return this.hasEnded;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get the span duration in milliseconds (only available after end())
|
|
78
|
+
*/
|
|
79
|
+
getDuration() {
|
|
80
|
+
return this.duration;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/modules/APM.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CustomSpan } from '../models/CustomSpan';
|
|
1
2
|
/**
|
|
2
3
|
* Enables or disables APM
|
|
3
4
|
* @param isEnabled
|
|
@@ -80,3 +81,60 @@ export declare const _lcqSleep: () => void;
|
|
|
80
81
|
* @param isEnabled
|
|
81
82
|
*/
|
|
82
83
|
export declare const setScreenRenderingEnabled: (isEnabled: boolean) => void;
|
|
84
|
+
/**
|
|
85
|
+
* Starts a custom span for performance tracking.
|
|
86
|
+
*
|
|
87
|
+
* A custom span measures the duration of an arbitrary operation that is not
|
|
88
|
+
* automatically tracked by the SDK. The span must be manually ended by calling
|
|
89
|
+
* the `end()` method on the returned span object.
|
|
90
|
+
*
|
|
91
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
92
|
+
* Leading and trailing whitespace will be trimmed.
|
|
93
|
+
*
|
|
94
|
+
* @returns Promise<CustomSpan | null> - The span object to end later, or null if:
|
|
95
|
+
* - Name is empty after trimming
|
|
96
|
+
* - SDK is not initialized
|
|
97
|
+
* - APM is disabled
|
|
98
|
+
* - Custom spans feature is disabled
|
|
99
|
+
* - Maximum concurrent spans limit (100) reached
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const span = await APM.startCustomSpan('Load User Profile');
|
|
104
|
+
* if (span) {
|
|
105
|
+
* try {
|
|
106
|
+
* // ... perform operation ...
|
|
107
|
+
* } finally {
|
|
108
|
+
* await span.end();
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare const startCustomSpan: (name: string) => Promise<CustomSpan | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Records a completed custom span with pre-recorded timestamps.
|
|
116
|
+
*
|
|
117
|
+
* Use this method when you have already recorded the start and end times
|
|
118
|
+
* of an operation and want to report it retroactively.
|
|
119
|
+
*
|
|
120
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
121
|
+
* Leading and trailing whitespace will be trimmed.
|
|
122
|
+
* @param startDate - The start time of the operation
|
|
123
|
+
* @param endDate - The end time of the operation (must be after startDate)
|
|
124
|
+
*
|
|
125
|
+
* @returns Promise<void> - Resolves when the span has been recorded, or logs error if:
|
|
126
|
+
* - Name is empty after trimming
|
|
127
|
+
* - End date is not after start date
|
|
128
|
+
* - SDK is not initialized
|
|
129
|
+
* - APM is disabled
|
|
130
|
+
* - Custom spans feature is disabled
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const start = new Date();
|
|
135
|
+
* // ... operation already completed ...
|
|
136
|
+
* const end = new Date();
|
|
137
|
+
* await APM.addCompletedCustomSpan('Cache Lookup', start, end);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare const addCompletedCustomSpan: (name: string, startDate: Date, endDate: Date) => Promise<void>;
|
package/dist/modules/APM.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
import { NativeAPM } from '../native/NativeAPM';
|
|
3
3
|
import { NativeLuciq } from '../native/NativeLuciq';
|
|
4
|
+
import { startCustomSpan as startCustomSpanInternal, addCompletedCustomSpan as addCompletedCustomSpanInternal, } from '../utils/CustomSpansManager';
|
|
4
5
|
/**
|
|
5
6
|
* Enables or disables APM
|
|
6
7
|
* @param isEnabled
|
|
@@ -109,3 +110,64 @@ export const _lcqSleep = () => {
|
|
|
109
110
|
export const setScreenRenderingEnabled = (isEnabled) => {
|
|
110
111
|
NativeAPM.setScreenRenderingEnabled(isEnabled);
|
|
111
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* Starts a custom span for performance tracking.
|
|
115
|
+
*
|
|
116
|
+
* A custom span measures the duration of an arbitrary operation that is not
|
|
117
|
+
* automatically tracked by the SDK. The span must be manually ended by calling
|
|
118
|
+
* the `end()` method on the returned span object.
|
|
119
|
+
*
|
|
120
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
121
|
+
* Leading and trailing whitespace will be trimmed.
|
|
122
|
+
*
|
|
123
|
+
* @returns Promise<CustomSpan | null> - The span object to end later, or null if:
|
|
124
|
+
* - Name is empty after trimming
|
|
125
|
+
* - SDK is not initialized
|
|
126
|
+
* - APM is disabled
|
|
127
|
+
* - Custom spans feature is disabled
|
|
128
|
+
* - Maximum concurrent spans limit (100) reached
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const span = await APM.startCustomSpan('Load User Profile');
|
|
133
|
+
* if (span) {
|
|
134
|
+
* try {
|
|
135
|
+
* // ... perform operation ...
|
|
136
|
+
* } finally {
|
|
137
|
+
* await span.end();
|
|
138
|
+
* }
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export const startCustomSpan = async (name) => {
|
|
143
|
+
return startCustomSpanInternal(name);
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Records a completed custom span with pre-recorded timestamps.
|
|
147
|
+
*
|
|
148
|
+
* Use this method when you have already recorded the start and end times
|
|
149
|
+
* of an operation and want to report it retroactively.
|
|
150
|
+
*
|
|
151
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
152
|
+
* Leading and trailing whitespace will be trimmed.
|
|
153
|
+
* @param startDate - The start time of the operation
|
|
154
|
+
* @param endDate - The end time of the operation (must be after startDate)
|
|
155
|
+
*
|
|
156
|
+
* @returns Promise<void> - Resolves when the span has been recorded, or logs error if:
|
|
157
|
+
* - Name is empty after trimming
|
|
158
|
+
* - End date is not after start date
|
|
159
|
+
* - SDK is not initialized
|
|
160
|
+
* - APM is disabled
|
|
161
|
+
* - Custom spans feature is disabled
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const start = new Date();
|
|
166
|
+
* // ... operation already completed ...
|
|
167
|
+
* const end = new Date();
|
|
168
|
+
* await APM.addCompletedCustomSpan('Cache Lookup', start, end);
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export const addCompletedCustomSpan = async (name, startDate, endDate) => {
|
|
172
|
+
return addCompletedCustomSpanInternal(name, startDate, endDate);
|
|
173
|
+
};
|
|
@@ -14,6 +14,9 @@ export interface ApmNativeModule extends NativeModule {
|
|
|
14
14
|
endUITrace(): void;
|
|
15
15
|
lcqSleep(): void;
|
|
16
16
|
setScreenRenderingEnabled(isEnabled: boolean): void;
|
|
17
|
+
syncCustomSpan(name: string, startTimestamp: number, endTimestamp: number): Promise<void>;
|
|
18
|
+
isCustomSpanEnabled(): Promise<boolean>;
|
|
19
|
+
isAPMEnabled(): Promise<boolean>;
|
|
17
20
|
}
|
|
18
21
|
export declare const NativeAPM: ApmNativeModule;
|
|
19
22
|
export declare const emitter: NativeEventEmitter;
|
|
@@ -8,6 +8,7 @@ import type { ThemeConfig } from '../models/ThemeConfig';
|
|
|
8
8
|
export interface LuciqNativeModule extends NativeModule {
|
|
9
9
|
getConstants(): NativeConstants;
|
|
10
10
|
setEnabled(isEnabled: boolean): void;
|
|
11
|
+
isBuilt(): Promise<boolean>;
|
|
11
12
|
init(token: string, invocationEvents: InvocationEvent[], debugLogsLevel: LogLevel, useNativeNetworkInterception: boolean, codePushVersion?: string, appVariant?: string, options?: {
|
|
12
13
|
ignoreAndroidSecureFlag?: boolean;
|
|
13
14
|
}, overAirVersion?: OverAirUpdate): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CustomSpan } from '../models/CustomSpan';
|
|
2
|
+
/**
|
|
3
|
+
* Starts a custom span for performance tracking.
|
|
4
|
+
*
|
|
5
|
+
* A custom span measures the duration of an arbitrary operation that is not
|
|
6
|
+
* automatically tracked by the SDK. The span must be manually ended by calling
|
|
7
|
+
* the `end()` method on the returned span object.
|
|
8
|
+
*
|
|
9
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
10
|
+
* Leading and trailing whitespace will be trimmed.
|
|
11
|
+
*
|
|
12
|
+
* @returns Promise<CustomSpan | null> - The span object to end later, or null if:
|
|
13
|
+
* - Name is empty after trimming
|
|
14
|
+
* - SDK is not initialized
|
|
15
|
+
* - APM is disabled
|
|
16
|
+
* - Custom spans feature is disabled
|
|
17
|
+
* - Maximum concurrent spans limit (100) reached
|
|
18
|
+
*/
|
|
19
|
+
export declare const startCustomSpan: (name: string) => Promise<CustomSpan | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Records a completed custom span with pre-recorded timestamps.
|
|
22
|
+
*
|
|
23
|
+
* Use this method when you have already recorded the start and end times
|
|
24
|
+
* of an operation and want to report it retroactively.
|
|
25
|
+
*
|
|
26
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
27
|
+
* Leading and trailing whitespace will be trimmed.
|
|
28
|
+
* @param startDate - The start time of the operation
|
|
29
|
+
* @param endDate - The end time of the operation (must be after startDate)
|
|
30
|
+
*
|
|
31
|
+
* @returns Promise<void>
|
|
32
|
+
*/
|
|
33
|
+
export declare const addCompletedCustomSpan: (name: string, startDate: Date, endDate: Date) => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Test-only helper to clear active spans between tests.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export declare const __resetCustomSpansForTests: () => void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { NativeAPM } from '../native/NativeAPM';
|
|
2
|
+
import { NativeLuciq } from '../native/NativeLuciq';
|
|
3
|
+
import { CustomSpan } from '../models/CustomSpan';
|
|
4
|
+
import { LuciqStrings } from '../constants/Strings';
|
|
5
|
+
/**
|
|
6
|
+
* Tracks currently active custom spans
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
const activeSpans = new Set();
|
|
10
|
+
/**
|
|
11
|
+
* Maximum concurrent custom spans allowed at any time
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
const MAX_CONCURRENT_SPANS = 100;
|
|
15
|
+
/**
|
|
16
|
+
* Internal: unregister a span from active tracking
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
const unregisterSpan = (span) => {
|
|
20
|
+
activeSpans.delete(span);
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Internal: sync custom span data to native SDK
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
const syncCustomSpan = async (name, startTimestamp, endTimestamp) => {
|
|
27
|
+
// Validate inputs (safety net)
|
|
28
|
+
if (!name || name.trim().length === 0) {
|
|
29
|
+
console.error(LuciqStrings.customSpanNameEmpty);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (endTimestamp <= startTimestamp) {
|
|
33
|
+
console.error(LuciqStrings.customSpanEndTimeBeforeStartTime);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Truncate name if needed (safety net)
|
|
37
|
+
let spanName = name.trim();
|
|
38
|
+
if (spanName.length > 150) {
|
|
39
|
+
spanName = spanName.substring(0, 150);
|
|
40
|
+
}
|
|
41
|
+
await NativeAPM.syncCustomSpan(spanName, startTimestamp, endTimestamp);
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Starts a custom span for performance tracking.
|
|
45
|
+
*
|
|
46
|
+
* A custom span measures the duration of an arbitrary operation that is not
|
|
47
|
+
* automatically tracked by the SDK. The span must be manually ended by calling
|
|
48
|
+
* the `end()` method on the returned span object.
|
|
49
|
+
*
|
|
50
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
51
|
+
* Leading and trailing whitespace will be trimmed.
|
|
52
|
+
*
|
|
53
|
+
* @returns Promise<CustomSpan | null> - The span object to end later, or null if:
|
|
54
|
+
* - Name is empty after trimming
|
|
55
|
+
* - SDK is not initialized
|
|
56
|
+
* - APM is disabled
|
|
57
|
+
* - Custom spans feature is disabled
|
|
58
|
+
* - Maximum concurrent spans limit (100) reached
|
|
59
|
+
*/
|
|
60
|
+
export const startCustomSpan = async (name) => {
|
|
61
|
+
try {
|
|
62
|
+
// Validate name
|
|
63
|
+
const trimmedName = name.trim();
|
|
64
|
+
if (trimmedName.length === 0) {
|
|
65
|
+
console.error(LuciqStrings.customSpanNameEmpty);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// Check SDK initialization
|
|
69
|
+
const isInitialized = await NativeLuciq.isBuilt();
|
|
70
|
+
if (!isInitialized) {
|
|
71
|
+
console.error(LuciqStrings.customSpanSDKNotInitializedMessage);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Check APM enabled
|
|
75
|
+
const isAPMEnabled = await NativeAPM.isAPMEnabled();
|
|
76
|
+
if (!isAPMEnabled) {
|
|
77
|
+
console.log(LuciqStrings.customSpanAPMDisabledMessage);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Check custom spans enabled
|
|
81
|
+
const isCustomSpanEnabled = await NativeAPM.isCustomSpanEnabled();
|
|
82
|
+
if (!isCustomSpanEnabled) {
|
|
83
|
+
console.log(LuciqStrings.customSpanDisabled);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
// Check concurrent span limit
|
|
87
|
+
if (activeSpans.size >= MAX_CONCURRENT_SPANS) {
|
|
88
|
+
console.error(LuciqStrings.customSpanLimitReached);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// Truncate name if needed
|
|
92
|
+
let spanName = trimmedName;
|
|
93
|
+
if (spanName.length > 150) {
|
|
94
|
+
spanName = spanName.substring(0, 150);
|
|
95
|
+
console.log(LuciqStrings.customSpanNameTruncated);
|
|
96
|
+
}
|
|
97
|
+
// Create and register span with callbacks
|
|
98
|
+
const span = new CustomSpan(spanName, unregisterSpan, syncCustomSpan);
|
|
99
|
+
activeSpans.add(span);
|
|
100
|
+
return span;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error('[CustomSpan] Error starting span:', error);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Records a completed custom span with pre-recorded timestamps.
|
|
109
|
+
*
|
|
110
|
+
* Use this method when you have already recorded the start and end times
|
|
111
|
+
* of an operation and want to report it retroactively.
|
|
112
|
+
*
|
|
113
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
114
|
+
* Leading and trailing whitespace will be trimmed.
|
|
115
|
+
* @param startDate - The start time of the operation
|
|
116
|
+
* @param endDate - The end time of the operation (must be after startDate)
|
|
117
|
+
*
|
|
118
|
+
* @returns Promise<void>
|
|
119
|
+
*/
|
|
120
|
+
export const addCompletedCustomSpan = async (name, startDate, endDate) => {
|
|
121
|
+
try {
|
|
122
|
+
// Validate name
|
|
123
|
+
const trimmedName = name.trim();
|
|
124
|
+
if (trimmedName.length === 0) {
|
|
125
|
+
console.error(LuciqStrings.customSpanNameEmpty);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Validate timestamps
|
|
129
|
+
if (endDate <= startDate) {
|
|
130
|
+
console.error(LuciqStrings.customSpanEndTimeBeforeStartTime);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Check SDK initialization
|
|
134
|
+
const isInitialized = await NativeLuciq.isBuilt();
|
|
135
|
+
if (!isInitialized) {
|
|
136
|
+
console.error(LuciqStrings.customSpanSDKNotInitializedMessage);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Check APM enabled
|
|
140
|
+
const isAPMEnabled = await NativeAPM.isAPMEnabled();
|
|
141
|
+
if (!isAPMEnabled) {
|
|
142
|
+
console.log(LuciqStrings.customSpanAPMDisabledMessage);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Check custom spans enabled
|
|
146
|
+
const isCustomSpanEnabled = await NativeAPM.isCustomSpanEnabled();
|
|
147
|
+
if (!isCustomSpanEnabled) {
|
|
148
|
+
console.log(LuciqStrings.customSpanDisabled);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Truncate name if needed
|
|
152
|
+
let spanName = trimmedName;
|
|
153
|
+
if (spanName.length > 150) {
|
|
154
|
+
spanName = spanName.substring(0, 150);
|
|
155
|
+
console.log(LuciqStrings.customSpanNameTruncated);
|
|
156
|
+
}
|
|
157
|
+
// Convert to microseconds
|
|
158
|
+
const startMicros = startDate.getTime() * 1000;
|
|
159
|
+
const endMicros = endDate.getTime() * 1000;
|
|
160
|
+
// Send to native SDK
|
|
161
|
+
await syncCustomSpan(spanName, startMicros, endMicros);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error('[CustomSpan] Error adding completed span:', error);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Test-only helper to clear active spans between tests.
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
export const __resetCustomSpansForTests = () => {
|
|
172
|
+
activeSpans.clear();
|
|
173
|
+
};
|
|
@@ -23,4 +23,17 @@
|
|
|
23
23
|
|
|
24
24
|
- (void)setScreenRenderingEnabled:(BOOL)isEnabled;
|
|
25
25
|
|
|
26
|
+
// Custom Span methods
|
|
27
|
+
- (void)syncCustomSpan:(NSString *)name
|
|
28
|
+
startTimestamp:(double)startTimestamp
|
|
29
|
+
endTimestamp:(double)endTimestamp
|
|
30
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
31
|
+
rejecter:(RCTPromiseRejectBlock)reject;
|
|
32
|
+
|
|
33
|
+
- (void)isCustomSpanEnabled:(RCTPromiseResolveBlock)resolve
|
|
34
|
+
rejecter:(RCTPromiseRejectBlock)reject;
|
|
35
|
+
|
|
36
|
+
- (void)isAPMEnabled:(RCTPromiseResolveBlock)resolve
|
|
37
|
+
rejecter:(RCTPromiseRejectBlock)reject;
|
|
38
|
+
|
|
26
39
|
@end
|
|
@@ -90,6 +90,61 @@ RCT_EXPORT_METHOD(setScreenRenderingEnabled:(BOOL)isEnabled) {
|
|
|
90
90
|
LCQAPM.screenRenderingEnabled = isEnabled;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// Syncs a custom span to the native SDK (currently logs only)
|
|
94
|
+
RCT_EXPORT_METHOD(syncCustomSpan:(NSString *)name
|
|
95
|
+
startTimestamp:(double)startTimestamp
|
|
96
|
+
endTimestamp:(double)endTimestamp
|
|
97
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
98
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
99
|
+
{
|
|
100
|
+
@try {
|
|
101
|
+
// Convert microseconds → seconds (NSDate uses seconds)
|
|
102
|
+
NSTimeInterval startSeconds = startTimestamp / 1e6;
|
|
103
|
+
NSTimeInterval endSeconds = endTimestamp / 1e6;
|
|
104
|
+
|
|
105
|
+
NSDate *startDate = [NSDate dateWithTimeIntervalSince1970:startSeconds];
|
|
106
|
+
NSDate *endDate = [NSDate dateWithTimeIntervalSince1970:endSeconds];
|
|
107
|
+
|
|
108
|
+
// Add completed span to APM
|
|
109
|
+
[LCQAPM addCompletedCustomSpanWithName:name
|
|
110
|
+
startDate:startDate
|
|
111
|
+
endDate:endDate];
|
|
112
|
+
|
|
113
|
+
resolve(@YES);
|
|
114
|
+
}
|
|
115
|
+
@catch (NSException *exception) {
|
|
116
|
+
reject(
|
|
117
|
+
@"SYNC_CUSTOM_SPAN_ERROR",
|
|
118
|
+
exception.reason ?: @"Failed to sync custom span",
|
|
119
|
+
nil
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Checks if custom spans feature is enabled
|
|
125
|
+
RCT_EXPORT_METHOD(isCustomSpanEnabled:(RCTPromiseResolveBlock)resolve
|
|
126
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
127
|
+
@try {
|
|
128
|
+
BOOL enabled = LCQAPM.customSpansEnabled;
|
|
129
|
+
resolve(@(enabled));
|
|
130
|
+
} @catch (NSException *exception) {
|
|
131
|
+
NSLog(@"[CustomSpan] Error checking feature flag: %@", exception);
|
|
132
|
+
resolve(@NO);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Checks if APM is enabled
|
|
137
|
+
RCT_EXPORT_METHOD(isAPMEnabled:(RCTPromiseResolveBlock)resolve
|
|
138
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
139
|
+
@try {
|
|
140
|
+
BOOL enabled = LCQAPM.enabled;
|
|
141
|
+
resolve(@(enabled));
|
|
142
|
+
} @catch (NSException *exception) {
|
|
143
|
+
NSLog(@"[CustomSpan] Error checking APM enabled: %@", exception);
|
|
144
|
+
resolve(@NO);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
93
148
|
|
|
94
149
|
|
|
95
150
|
@synthesize description;
|
|
@@ -545,4 +545,16 @@ RCT_EXPORT_METHOD(setNetworkLogBodyEnabled:(BOOL)isEnabled) {
|
|
|
545
545
|
LCQNetworkLogger.logBodyEnabled = isEnabled;
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
+
// Checks if Luciq SDK is initialized
|
|
549
|
+
RCT_EXPORT_METHOD(isBuilt:(RCTPromiseResolveBlock)resolve
|
|
550
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
551
|
+
@try {
|
|
552
|
+
BOOL isBuilt = YES;
|
|
553
|
+
resolve(@(isBuilt));
|
|
554
|
+
} @catch (NSException *exception) {
|
|
555
|
+
NSLog(@"[Luciq] Error checking if SDK is built: %@", exception);
|
|
556
|
+
resolve(@NO);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
548
560
|
@end
|
package/ios/native.rb
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luciq/react-native",
|
|
3
3
|
"description": "Luciq is the Agentic Observability Platform built for Mobile.",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.3.0",
|
|
5
5
|
"author": "Luciq (https://luciq.ai)",
|
|
6
6
|
"repository": "github:luciqai/luciq-reactnative-sdk",
|
|
7
7
|
"homepage": "https://www.luciq.ai/platforms/react-native",
|
package/plugin/build/index.js
CHANGED
|
@@ -42044,9 +42044,16 @@ function addLuciqBuildPhase(xcodeProject, packageName) {
|
|
|
42044
42044
|
// Inject source map export line into the shell script
|
|
42045
42045
|
function injectSourceMapExport(script) {
|
|
42046
42046
|
var exportLine = 'export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"';
|
|
42047
|
-
var escapedLine = exportLine.replace(
|
|
42047
|
+
var escapedLine = exportLine.replace(/"/g, '\\"');
|
|
42048
42048
|
var injectedLine = "".concat(escapedLine, "\\n");
|
|
42049
|
-
|
|
42049
|
+
if (script.includes(escapedLine)) {
|
|
42050
|
+
return script;
|
|
42051
|
+
}
|
|
42052
|
+
var buggyLine = exportLine.replace(/\$/g, '\\$').replace(/"/g, '\\"');
|
|
42053
|
+
if (script.includes(buggyLine)) {
|
|
42054
|
+
return script.split(buggyLine).join(escapedLine);
|
|
42055
|
+
}
|
|
42056
|
+
return script.replace(/^"/, "\"".concat(injectedLine));
|
|
42050
42057
|
}
|
|
42051
42058
|
|
|
42052
42059
|
var luciqPackage = require('../../package.json');
|
|
@@ -102,8 +102,15 @@ function addLuciqBuildPhase(xcodeProject: XcodeProject, packageName: string): vo
|
|
|
102
102
|
// Inject source map export line into the shell script
|
|
103
103
|
function injectSourceMapExport(script: string): string {
|
|
104
104
|
const exportLine = 'export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"';
|
|
105
|
-
const escapedLine = exportLine.replace(
|
|
105
|
+
const escapedLine = exportLine.replace(/"/g, '\\"');
|
|
106
106
|
const injectedLine = `${escapedLine}\\n`;
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
if (script.includes(escapedLine)) {
|
|
109
|
+
return script;
|
|
110
|
+
}
|
|
111
|
+
const buggyLine = exportLine.replace(/\$/g, '\\$').replace(/"/g, '\\"');
|
|
112
|
+
if (script.includes(buggyLine)) {
|
|
113
|
+
return script.split(buggyLine).join(escapedLine);
|
|
114
|
+
}
|
|
115
|
+
return script.replace(/^"/, `"${injectedLine}`);
|
|
109
116
|
}
|