@miradorlabs/parallax-web 2.0.1 → 2.2.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/.claude/settings.local.json +8 -2
- package/CLAUDE.md +56 -0
- package/README.md +106 -49
- package/dist/index.d.ts +103 -36
- package/dist/index.esm.js +1650 -415
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1650 -415
- package/dist/index.umd.js.map +1 -1
- package/example/README.md +1 -1
- package/example/app.js +3 -3
- package/example/proxy-server.js +1 -1
- package/package.json +2 -2
- package/src/parallax/client.ts +32 -29
- package/src/parallax/index.ts +2 -1
- package/src/parallax/trace.ts +268 -65
- package/src/parallax/types.ts +21 -3
- package/tests/parallax.test.ts +534 -174
package/example/README.md
CHANGED
package/example/app.js
CHANGED
|
@@ -3,11 +3,11 @@ import { ParallaxClient } from '../dist/index.esm.js';
|
|
|
3
3
|
|
|
4
4
|
// Initialize the client
|
|
5
5
|
// Use proxy to avoid CORS issues (default)
|
|
6
|
-
// To use direct connection, change to: 'https://parallax-gateway
|
|
6
|
+
// To use direct connection, change to: 'https://parallax-gateway-dev.mirador.org:443'
|
|
7
7
|
const USE_PROXY = true;
|
|
8
|
-
const GATEWAY_URL = USE_PROXY
|
|
8
|
+
const GATEWAY_URL = USE_PROXY
|
|
9
9
|
? 'http://localhost:3001' // Proxy server (no CORS issues)
|
|
10
|
-
: 'https://parallax-gateway
|
|
10
|
+
: 'https://parallax-gateway-dev.mirador.org:443'; // Direct (may have CORS issues)
|
|
11
11
|
|
|
12
12
|
const client = new ParallaxClient('demo-api-key', GATEWAY_URL);
|
|
13
13
|
|
package/example/proxy-server.js
CHANGED
|
@@ -11,7 +11,7 @@ const app = express();
|
|
|
11
11
|
const PORT = 3001;
|
|
12
12
|
|
|
13
13
|
// Target Parallax Gateway
|
|
14
|
-
const GATEWAY_URL = 'https://parallax-gateway
|
|
14
|
+
const GATEWAY_URL = 'https://parallax-gateway-dev.mirador.org:443';
|
|
15
15
|
|
|
16
16
|
// Enable CORS for all origins (development only)
|
|
17
17
|
app.use(cors({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miradorlabs/parallax-web",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Parallax Web Client Side SDK ",
|
|
6
6
|
"main": "dist/index.umd.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"google-protobuf": "^3.21.4",
|
|
52
52
|
"grpc-web": "^2.0.2",
|
|
53
|
-
"mirador-gateway-parallax-web": "https://storage.googleapis.com/mirador-shd-packages/gateway/parallax/grpc-web/mirador-gateway-parallax-grpc-web-1.0.
|
|
53
|
+
"mirador-gateway-parallax-web": "https://storage.googleapis.com/mirador-shd-packages/gateway/parallax/grpc-web/mirador-gateway-parallax-grpc-web-1.0.14.tgz",
|
|
54
54
|
"rxjs": "^7.8.2"
|
|
55
55
|
},
|
|
56
56
|
"author": "@mirador",
|
package/src/parallax/client.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ParallaxClient - Main client for interacting with the Parallax Gateway
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CreateTraceRequest,
|
|
6
|
+
UpdateTraceRequest,
|
|
7
|
+
} from 'mirador-gateway-parallax-web/proto/gateway/parallax/v1/parallax_gateway_pb';
|
|
5
8
|
import { ParallaxGatewayServiceClient } from 'mirador-gateway-parallax-web/proto/gateway/parallax/v1/Parallax_gatewayServiceClientPb';
|
|
6
9
|
import { ParallaxTrace } from './trace';
|
|
10
|
+
import type { ParallaxClientOptions, TraceOptions } from './types';
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
// Default configuration values
|
|
13
|
+
const DEFAULT_API_URL = 'https://parallax-gateway-dev.mirador.org:443';
|
|
14
|
+
const DEFAULT_AUTO_FLUSH = true;
|
|
15
|
+
const DEFAULT_FLUSH_PERIOD_MS = 50;
|
|
16
|
+
const DEFAULT_INCLUDE_CLIENT_META = true;
|
|
17
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
18
|
+
const DEFAULT_RETRY_BACKOFF = 1000;
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* Main client for interacting with the Parallax Gateway API
|
|
@@ -18,49 +28,42 @@ export class ParallaxClient {
|
|
|
18
28
|
/**
|
|
19
29
|
* Create a new ParallaxClient instance
|
|
20
30
|
* @param apiKey Required API key for authentication (sent as x-parallax-api-key header)
|
|
21
|
-
* @param
|
|
31
|
+
* @param options Optional configuration options
|
|
22
32
|
*/
|
|
23
|
-
constructor(apiKey: string,
|
|
33
|
+
constructor(apiKey: string, options?: ParallaxClientOptions) {
|
|
24
34
|
this.apiKey = apiKey;
|
|
25
|
-
this.apiUrl = apiUrl || DEFAULT_API_URL;
|
|
35
|
+
this.apiUrl = options?.apiUrl || DEFAULT_API_URL;
|
|
26
36
|
|
|
27
|
-
// API key is required - always include in credentials
|
|
28
37
|
const credentials = { 'x-parallax-api-key': apiKey };
|
|
29
|
-
|
|
30
|
-
// Initialize the gRPC-Web client
|
|
31
38
|
this.client = new ParallaxGatewayServiceClient(this.apiUrl, credentials);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
/**
|
|
35
|
-
* Internal method to send trace to gateway
|
|
36
|
-
* @internal
|
|
37
|
-
*/
|
|
41
|
+
/** @internal */
|
|
38
42
|
async _sendTrace(request: CreateTraceRequest) {
|
|
39
43
|
const metadata = { 'x-parallax-api-key': this.apiKey };
|
|
40
44
|
return await this.client.createTrace(request, metadata);
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
/** @internal */
|
|
48
|
+
async _updateTrace(request: UpdateTraceRequest) {
|
|
49
|
+
const metadata = { 'x-parallax-api-key': this.apiKey };
|
|
50
|
+
return await this.client.updateTrace(request, metadata);
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
/**
|
|
44
54
|
* Create a new trace builder
|
|
45
55
|
*
|
|
46
|
-
*
|
|
47
|
-
* ```typescript
|
|
48
|
-
* const response = await client.trace("swap_execution")
|
|
49
|
-
* .addAttribute("user", "0xabc...")
|
|
50
|
-
* .addAttribute("slippage_bps", 25)
|
|
51
|
-
* .addTag("dex")
|
|
52
|
-
* .addTag("swap")
|
|
53
|
-
* .addEvent("wallet_connected", { wallet: "MetaMask" })
|
|
54
|
-
* .addEvent("quote_received")
|
|
55
|
-
* .setTxHint("0x123...", "ethereum")
|
|
56
|
-
* .create();
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @param name Optional name of the trace (defaults to empty string)
|
|
60
|
-
* @param includeClientMeta Optional flag to automatically include client metadata
|
|
56
|
+
* @param options Trace configuration options
|
|
61
57
|
* @returns A ParallaxTrace builder instance
|
|
62
58
|
*/
|
|
63
|
-
trace(
|
|
64
|
-
return new ParallaxTrace(this,
|
|
59
|
+
trace(options?: TraceOptions): ParallaxTrace {
|
|
60
|
+
return new ParallaxTrace(this, {
|
|
61
|
+
name: options?.name,
|
|
62
|
+
autoFlush: options?.autoFlush ?? DEFAULT_AUTO_FLUSH,
|
|
63
|
+
flushPeriodMs: options?.flushPeriodMs ?? DEFAULT_FLUSH_PERIOD_MS,
|
|
64
|
+
includeClientMeta: options?.includeClientMeta ?? DEFAULT_INCLUDE_CLIENT_META,
|
|
65
|
+
maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
66
|
+
retryBackoff: options?.retryBackoff ?? DEFAULT_RETRY_BACKOFF,
|
|
67
|
+
});
|
|
65
68
|
}
|
|
66
69
|
}
|
package/src/parallax/index.ts
CHANGED
package/src/parallax/trace.ts
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
import {
|
|
5
5
|
CreateTraceRequest,
|
|
6
6
|
CreateTraceResponse,
|
|
7
|
+
UpdateTraceRequest,
|
|
8
|
+
UpdateTraceResponse,
|
|
9
|
+
TraceData,
|
|
10
|
+
Attributes,
|
|
11
|
+
Tags,
|
|
7
12
|
Event,
|
|
8
13
|
TxHashHint as TxHashHintProto,
|
|
9
14
|
Chain,
|
|
@@ -31,25 +36,78 @@ const CHAIN_MAP: Record<ChainName, Chain> = {
|
|
|
31
36
|
*/
|
|
32
37
|
export interface TraceSubmitter {
|
|
33
38
|
_sendTrace(request: CreateTraceRequest): Promise<CreateTraceResponse>;
|
|
39
|
+
_updateTrace(request: UpdateTraceRequest): Promise<UpdateTraceResponse>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Options passed to ParallaxTrace constructor (with defaults applied) */
|
|
43
|
+
interface ResolvedTraceOptions {
|
|
44
|
+
name?: string;
|
|
45
|
+
autoFlush: boolean;
|
|
46
|
+
flushPeriodMs: number;
|
|
47
|
+
includeClientMeta: boolean;
|
|
48
|
+
maxRetries: number;
|
|
49
|
+
retryBackoff: number;
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
/**
|
|
37
|
-
* Builder class for constructing traces with method chaining
|
|
38
|
-
*
|
|
53
|
+
* Builder class for constructing traces with method chaining.
|
|
54
|
+
* Supports auto-flush mode (default) where data is automatically sent after a period of inactivity,
|
|
55
|
+
* or manual flush mode where you explicitly call flush().
|
|
39
56
|
*/
|
|
40
57
|
export class ParallaxTrace {
|
|
41
|
-
private name
|
|
42
|
-
private attributes: { [key: string]: string } = {};
|
|
43
|
-
private tags: string[] = [];
|
|
44
|
-
private events: TraceEvent[] = [];
|
|
45
|
-
private txHashHint?: TxHashHint;
|
|
58
|
+
private name?: string;
|
|
46
59
|
private client: TraceSubmitter;
|
|
47
60
|
private includeClientMeta: boolean;
|
|
48
61
|
|
|
49
|
-
|
|
62
|
+
// Flush configuration
|
|
63
|
+
private autoFlush: boolean;
|
|
64
|
+
private flushPeriodMs: number;
|
|
65
|
+
private flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
66
|
+
|
|
67
|
+
// Retry configuration
|
|
68
|
+
private maxRetries: number;
|
|
69
|
+
private retryBackoff: number;
|
|
70
|
+
|
|
71
|
+
// State tracking
|
|
72
|
+
private traceId: string | null = null;
|
|
73
|
+
private pendingAttributes: { [key: string]: string } = {};
|
|
74
|
+
private pendingTags: string[] = [];
|
|
75
|
+
private pendingEvents: TraceEvent[] = [];
|
|
76
|
+
private pendingTxHashHints: TxHashHint[] = [];
|
|
77
|
+
|
|
78
|
+
// Queue for maintaining strict ordering of flushes
|
|
79
|
+
private flushQueue: Promise<void> = Promise.resolve();
|
|
80
|
+
|
|
81
|
+
constructor(client: TraceSubmitter, options: ResolvedTraceOptions) {
|
|
50
82
|
this.client = client;
|
|
51
|
-
this.name = name;
|
|
52
|
-
this.
|
|
83
|
+
this.name = options.name;
|
|
84
|
+
this.autoFlush = options.autoFlush;
|
|
85
|
+
this.flushPeriodMs = options.flushPeriodMs;
|
|
86
|
+
this.includeClientMeta = options.includeClientMeta;
|
|
87
|
+
this.maxRetries = options.maxRetries;
|
|
88
|
+
this.retryBackoff = options.retryBackoff;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Schedule an auto-flush after the configured period.
|
|
93
|
+
* Resets the timer on each call.
|
|
94
|
+
* If flushPeriodMs is 0, flushes immediately on every call.
|
|
95
|
+
*/
|
|
96
|
+
private scheduleFlush(): void {
|
|
97
|
+
if (!this.autoFlush) return;
|
|
98
|
+
|
|
99
|
+
// flushPeriodMs === 0 means flush immediately on every call
|
|
100
|
+
if (this.flushPeriodMs === 0) {
|
|
101
|
+
this.flush();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (this.flushTimer) {
|
|
106
|
+
clearTimeout(this.flushTimer);
|
|
107
|
+
}
|
|
108
|
+
this.flushTimer = setTimeout(() => {
|
|
109
|
+
this.flush();
|
|
110
|
+
}, this.flushPeriodMs);
|
|
53
111
|
}
|
|
54
112
|
|
|
55
113
|
/**
|
|
@@ -59,10 +117,11 @@ export class ParallaxTrace {
|
|
|
59
117
|
* @returns This trace builder for chaining
|
|
60
118
|
*/
|
|
61
119
|
addAttribute(key: string, value: string | number | boolean | object): this {
|
|
62
|
-
this.
|
|
120
|
+
this.pendingAttributes[key] =
|
|
63
121
|
typeof value === 'object' && value !== null
|
|
64
122
|
? JSON.stringify(value)
|
|
65
123
|
: String(value);
|
|
124
|
+
this.scheduleFlush();
|
|
66
125
|
return this;
|
|
67
126
|
}
|
|
68
127
|
|
|
@@ -73,11 +132,12 @@ export class ParallaxTrace {
|
|
|
73
132
|
*/
|
|
74
133
|
addAttributes(attributes: { [key: string]: string | number | boolean | object }): this {
|
|
75
134
|
for (const [key, value] of Object.entries(attributes)) {
|
|
76
|
-
this.
|
|
135
|
+
this.pendingAttributes[key] =
|
|
77
136
|
typeof value === 'object' && value !== null
|
|
78
137
|
? JSON.stringify(value)
|
|
79
138
|
: String(value);
|
|
80
139
|
}
|
|
140
|
+
this.scheduleFlush();
|
|
81
141
|
return this;
|
|
82
142
|
}
|
|
83
143
|
|
|
@@ -87,7 +147,8 @@ export class ParallaxTrace {
|
|
|
87
147
|
* @returns This trace builder for chaining
|
|
88
148
|
*/
|
|
89
149
|
addTag(tag: string): this {
|
|
90
|
-
this.
|
|
150
|
+
this.pendingTags.push(tag);
|
|
151
|
+
this.scheduleFlush();
|
|
91
152
|
return this;
|
|
92
153
|
}
|
|
93
154
|
|
|
@@ -97,7 +158,8 @@ export class ParallaxTrace {
|
|
|
97
158
|
* @returns This trace builder for chaining
|
|
98
159
|
*/
|
|
99
160
|
addTags(tags: string[]): this {
|
|
100
|
-
this.
|
|
161
|
+
this.pendingTags.push(...tags);
|
|
162
|
+
this.scheduleFlush();
|
|
101
163
|
return this;
|
|
102
164
|
}
|
|
103
165
|
|
|
@@ -113,97 +175,238 @@ export class ParallaxTrace {
|
|
|
113
175
|
? JSON.stringify(details)
|
|
114
176
|
: details;
|
|
115
177
|
|
|
116
|
-
this.
|
|
178
|
+
this.pendingEvents.push({
|
|
117
179
|
eventName,
|
|
118
180
|
details: detailsString,
|
|
119
181
|
timestamp: timestamp || new Date(),
|
|
120
182
|
});
|
|
183
|
+
this.scheduleFlush();
|
|
121
184
|
return this;
|
|
122
185
|
}
|
|
123
186
|
|
|
124
187
|
/**
|
|
125
|
-
*
|
|
188
|
+
* Add a transaction hash hint for blockchain correlation.
|
|
189
|
+
* Multiple hints can be added to the same trace.
|
|
126
190
|
* @param txHash Transaction hash
|
|
127
191
|
* @param chain Chain name (e.g., "ethereum", "polygon", "base")
|
|
128
192
|
* @param details Optional details about the transaction
|
|
129
193
|
* @returns This trace builder for chaining
|
|
130
194
|
*/
|
|
131
|
-
|
|
132
|
-
this.
|
|
195
|
+
addTxHint(txHash: string, chain: ChainName, details?: string): this {
|
|
196
|
+
this.pendingTxHashHints.push({
|
|
133
197
|
txHash,
|
|
134
198
|
chain,
|
|
135
199
|
details,
|
|
136
200
|
timestamp: new Date(),
|
|
137
|
-
};
|
|
201
|
+
});
|
|
202
|
+
this.scheduleFlush();
|
|
138
203
|
return this;
|
|
139
204
|
}
|
|
140
205
|
|
|
141
206
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
207
|
+
* Flush pending data to the gateway.
|
|
208
|
+
* Fire-and-forget - returns immediately but maintains strict ordering of requests.
|
|
209
|
+
* First flush calls CreateTrace, subsequent flushes call UpdateTrace.
|
|
144
210
|
*/
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
211
|
+
flush(): void {
|
|
212
|
+
// Cancel any pending auto-flush timer
|
|
213
|
+
if (this.flushTimer) {
|
|
214
|
+
clearTimeout(this.flushTimer);
|
|
215
|
+
this.flushTimer = null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if there's anything to flush
|
|
219
|
+
const hasPendingData =
|
|
220
|
+
Object.keys(this.pendingAttributes).length > 0 ||
|
|
221
|
+
this.pendingTags.length > 0 ||
|
|
222
|
+
this.pendingEvents.length > 0 ||
|
|
223
|
+
this.pendingTxHashHints.length > 0;
|
|
150
224
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
for (const [key, value] of Object.entries(this.attributes)) {
|
|
154
|
-
attrsMap.set(key, value);
|
|
225
|
+
if (!hasPendingData && this.traceId !== null) {
|
|
226
|
+
return; // Nothing to flush
|
|
155
227
|
}
|
|
156
228
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
229
|
+
// Capture pending data NOW (before it changes)
|
|
230
|
+
const traceData = this.buildTraceData();
|
|
231
|
+
|
|
232
|
+
// Capture context for error reporting
|
|
233
|
+
const isCreateOp = this.traceId === null;
|
|
234
|
+
const traceName = this.name;
|
|
235
|
+
|
|
236
|
+
// Clear pending immediately so next flush doesn't re-send
|
|
237
|
+
this.clearPending();
|
|
238
|
+
|
|
239
|
+
// Chain onto the queue for strict ordering
|
|
240
|
+
// Check traceId inside the queue to ensure proper ordering
|
|
241
|
+
this.flushQueue = this.flushQueue.then(async () => {
|
|
242
|
+
if (this.traceId === null) {
|
|
243
|
+
await this.createTrace(traceData);
|
|
244
|
+
} else {
|
|
245
|
+
await this.updateTrace(traceData);
|
|
162
246
|
}
|
|
247
|
+
}).catch(err => {
|
|
248
|
+
const operation = isCreateOp ? 'CreateTrace' : 'UpdateTrace';
|
|
249
|
+
const context = traceName ? ` (trace: ${traceName})` : '';
|
|
250
|
+
console.error(`[ParallaxTrace] Flush error during ${operation}${context}:`, err);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Build TraceData from pending state
|
|
256
|
+
*/
|
|
257
|
+
private buildTraceData(): TraceData {
|
|
258
|
+
const traceData = new TraceData();
|
|
259
|
+
|
|
260
|
+
// Add pending attributes (+ client metadata on first flush)
|
|
261
|
+
const allAttrs = { ...this.pendingAttributes };
|
|
262
|
+
if (this.traceId === null && this.includeClientMeta) {
|
|
263
|
+
const clientMeta = getClientMetadata();
|
|
264
|
+
for (const [key, value] of Object.entries(clientMeta)) {
|
|
265
|
+
allAttrs[`client.${key}`] = value;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (Object.keys(allAttrs).length > 0) {
|
|
269
|
+
const attrsMsg = new Attributes();
|
|
270
|
+
const attrsMap = attrsMsg.getAttributesMap();
|
|
271
|
+
for (const [key, value] of Object.entries(allAttrs)) {
|
|
272
|
+
attrsMap.set(key, value);
|
|
273
|
+
}
|
|
274
|
+
traceData.addAttributes(attrsMsg);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Add pending tags
|
|
278
|
+
if (this.pendingTags.length > 0) {
|
|
279
|
+
const tagsMsg = new Tags();
|
|
280
|
+
tagsMsg.setTagsList(this.pendingTags);
|
|
281
|
+
traceData.addTags(tagsMsg);
|
|
163
282
|
}
|
|
164
283
|
|
|
165
|
-
// Add events
|
|
166
|
-
const
|
|
167
|
-
for (const event of this.events) {
|
|
284
|
+
// Add pending events
|
|
285
|
+
for (const event of this.pendingEvents) {
|
|
168
286
|
const eventMsg = new Event();
|
|
169
287
|
eventMsg.setName(event.eventName);
|
|
170
288
|
if (event.details) {
|
|
171
289
|
eventMsg.setDetails(event.details);
|
|
172
290
|
}
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
eventMsg.setTimestamp(
|
|
176
|
-
|
|
291
|
+
const ts = new Timestamp();
|
|
292
|
+
ts.fromDate(event.timestamp);
|
|
293
|
+
eventMsg.setTimestamp(ts);
|
|
294
|
+
traceData.addEvents(eventMsg);
|
|
177
295
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
txHint.setDetails(this.txHashHint.details);
|
|
296
|
+
|
|
297
|
+
// Add pending tx hints
|
|
298
|
+
for (const hint of this.pendingTxHashHints) {
|
|
299
|
+
const hintMsg = new TxHashHintProto();
|
|
300
|
+
hintMsg.setTxHash(hint.txHash);
|
|
301
|
+
hintMsg.setChain(CHAIN_MAP[hint.chain]);
|
|
302
|
+
if (hint.details) {
|
|
303
|
+
hintMsg.setDetails(hint.details);
|
|
187
304
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
305
|
+
const ts = new Timestamp();
|
|
306
|
+
ts.fromDate(hint.timestamp);
|
|
307
|
+
hintMsg.setTimestamp(ts);
|
|
308
|
+
traceData.addTxHashHints(hintMsg);
|
|
192
309
|
}
|
|
193
310
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const responseStatus = response.getStatus();
|
|
311
|
+
return traceData;
|
|
312
|
+
}
|
|
197
313
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Sleep for the specified duration
|
|
316
|
+
*/
|
|
317
|
+
private sleep(ms: number): Promise<void> {
|
|
318
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Execute an operation with exponential backoff retry
|
|
323
|
+
*/
|
|
324
|
+
private async retryWithBackoff<T>(
|
|
325
|
+
operation: () => Promise<T>,
|
|
326
|
+
operationName: string
|
|
327
|
+
): Promise<T> {
|
|
328
|
+
let lastError: Error | undefined;
|
|
329
|
+
|
|
330
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
331
|
+
try {
|
|
332
|
+
return await operation();
|
|
333
|
+
} catch (err) {
|
|
334
|
+
lastError = err as Error;
|
|
335
|
+
|
|
336
|
+
if (attempt < this.maxRetries) {
|
|
337
|
+
const delay = this.retryBackoff * Math.pow(2, attempt);
|
|
338
|
+
console.warn(
|
|
339
|
+
`[ParallaxTrace] ${operationName} failed, retrying in ${delay}ms (attempt ${attempt + 1}/${this.maxRetries})`
|
|
340
|
+
);
|
|
341
|
+
await this.sleep(delay);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
throw lastError;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Send CreateTrace request
|
|
351
|
+
*/
|
|
352
|
+
private async createTrace(traceData: TraceData): Promise<void> {
|
|
353
|
+
const request = new CreateTraceRequest();
|
|
354
|
+
if (this.name) {
|
|
355
|
+
request.setName(this.name);
|
|
356
|
+
}
|
|
357
|
+
request.setData(traceData);
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const response = await this.retryWithBackoff(
|
|
361
|
+
() => this.client._sendTrace(request),
|
|
362
|
+
'CreateTrace'
|
|
363
|
+
);
|
|
364
|
+
if (response.getStatus()?.getCode() === ResponseStatus.StatusCode.STATUS_CODE_SUCCESS) {
|
|
365
|
+
this.traceId = response.getTraceId();
|
|
366
|
+
} else {
|
|
367
|
+
console.error('[ParallaxTrace] CreateTrace failed:', response.getStatus()?.getErrorMessage());
|
|
201
368
|
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
console.error('[ParallaxTrace] CreateTrace error after retries:', err);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
202
373
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
374
|
+
/**
|
|
375
|
+
* Send UpdateTrace request
|
|
376
|
+
*/
|
|
377
|
+
private async updateTrace(traceData: TraceData): Promise<void> {
|
|
378
|
+
const request = new UpdateTraceRequest();
|
|
379
|
+
request.setTraceId(this.traceId!);
|
|
380
|
+
request.setData(traceData);
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const response = await this.retryWithBackoff(
|
|
384
|
+
() => this.client._updateTrace(request),
|
|
385
|
+
'UpdateTrace'
|
|
386
|
+
);
|
|
387
|
+
if (response.getStatus()?.getCode() !== ResponseStatus.StatusCode.STATUS_CODE_SUCCESS) {
|
|
388
|
+
console.error('[ParallaxTrace] UpdateTrace failed:', response.getStatus()?.getErrorMessage());
|
|
389
|
+
}
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error('[ParallaxTrace] UpdateTrace error after retries:', err);
|
|
207
392
|
}
|
|
208
393
|
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Clear all pending data
|
|
397
|
+
*/
|
|
398
|
+
private clearPending(): void {
|
|
399
|
+
this.pendingAttributes = {};
|
|
400
|
+
this.pendingTags = [];
|
|
401
|
+
this.pendingEvents = [];
|
|
402
|
+
this.pendingTxHashHints = [];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get the trace ID (available after first flush completes)
|
|
407
|
+
* @returns The trace ID or null if not yet created
|
|
408
|
+
*/
|
|
409
|
+
getTraceId(): string | null {
|
|
410
|
+
return this.traceId;
|
|
411
|
+
}
|
|
209
412
|
}
|
package/src/parallax/types.ts
CHANGED
|
@@ -3,13 +3,31 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Options for ParallaxClient constructor
|
|
7
7
|
*/
|
|
8
|
-
export interface
|
|
9
|
-
|
|
8
|
+
export interface ParallaxClientOptions {
|
|
9
|
+
/** Gateway URL (defaults to parallax-gateway-dev.mirador.org:443) */
|
|
10
10
|
apiUrl?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Options for creating a trace
|
|
15
|
+
*/
|
|
16
|
+
export interface TraceOptions {
|
|
17
|
+
/** Trace name */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** Enable auto-flush mode (default: true) */
|
|
20
|
+
autoFlush?: boolean;
|
|
21
|
+
/** Debounce period in ms before auto-flush triggers (default: 50) */
|
|
22
|
+
flushPeriodMs?: number;
|
|
23
|
+
/** Include browser/OS metadata in first flush (default: true) */
|
|
24
|
+
includeClientMeta?: boolean;
|
|
25
|
+
/** Maximum number of retry attempts on failure (default: 3) */
|
|
26
|
+
maxRetries?: number;
|
|
27
|
+
/** Base delay in ms for exponential backoff between retries (default: 1000) */
|
|
28
|
+
retryBackoff?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
13
31
|
/**
|
|
14
32
|
* Client metadata collected from the browser environment
|
|
15
33
|
*/
|