@miradorlabs/parallax-web 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miradorlabs/parallax-web",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Parallax Web Client Side SDK ",
5
5
  "main": "dist/index.umd.js",
6
6
  "module": "dist/index.esm.js",
@@ -44,7 +44,7 @@
44
44
  "typescript-eslint": "^8.47.0"
45
45
  },
46
46
  "dependencies": {
47
- "google-protobuf": "^4.0.1",
47
+ "google-protobuf": "^3.21.4",
48
48
  "grpc-web": "^2.0.2",
49
49
  "mirador-gateway-parallax-web": "https://storage.googleapis.com/mirador-shd-packages/gateway/parallax/grpc-web/mirador-gateway-parallax-grpc-web-1.0.9.tgz",
50
50
  "rxjs": "^7.8.2"
@@ -38,6 +38,129 @@ class ParallaxClient {
38
38
  this.client = new ParallaxGatewayServiceClient(this.apiUrl, credentials);
39
39
  }
40
40
 
41
+ /**
42
+ * Gather client metadata for traces/spans
43
+ * Returns a metadata object with client environment details
44
+ * This includes browser, OS, screen size, IP address, and more
45
+ * @returns metadata
46
+ */
47
+ async getClientMetadata(): Promise<{ [key: string]: string }> {
48
+ const metadata: { [key: string]: string } = {};
49
+ // Browser info
50
+ metadata.userAgent = navigator.userAgent;
51
+ metadata.platform = navigator.platform;
52
+ metadata.language = navigator.language;
53
+
54
+ // Screen info
55
+ metadata.screenWidth = window.screen.width.toString();
56
+ metadata.screenHeight = window.screen.height.toString();
57
+ metadata.viewportWidth = window.innerWidth.toString();
58
+ metadata.viewportHeight = window.innerHeight.toString();
59
+
60
+ // Try to get IP address (non-blocking)
61
+ // Note: This may be blocked by Content Security Policy (CSP)
62
+ // If blocked, the backend should capture IP from request headers instead
63
+ try {
64
+ // Use ipify API (simple and fast JSON response)
65
+ const ipResponse: Response | Error = await fetch('https://api.ipify.org?format=json');
66
+
67
+ if (ipResponse && ipResponse.ok) {
68
+ const data = await ipResponse.json();
69
+ if (data && data.ip) {
70
+ metadata.ip = data.ip;
71
+ } else {
72
+ metadata.ip = 'client_unavailable';
73
+ }
74
+ } else {
75
+ metadata.ip = 'client_unavailable';
76
+ }
77
+ } catch (error) {
78
+ // IP lookup failed (CSP block, timeout, or network error)
79
+ // This is expected if CSP blocks external requests
80
+ // Backend should capture IP from request headers instead
81
+ if (error instanceof Error && error.message.includes('Content Security Policy')) {
82
+ console.debug('IP fetch blocked by CSP - backend will capture from headers');
83
+ } else {
84
+ console.debug('Could not fetch IP address');
85
+ }
86
+ metadata.ip = 'client_unavailable';
87
+ }
88
+
89
+ // Browser details (parse from user agent)
90
+ const ua = navigator.userAgent;
91
+ if (ua.includes('Chrome')) {
92
+ metadata.browser = 'Chrome';
93
+ } else if (ua.includes('Firefox')) {
94
+ metadata.browser = 'Firefox';
95
+ } else if (ua.includes('Safari')) {
96
+ metadata.browser = 'Safari';
97
+ } else if (ua.includes('Edge')) {
98
+ metadata.browser = 'Edge';
99
+ } else {
100
+ metadata.browser = 'Unknown';
101
+ }
102
+
103
+ // OS detection
104
+ if (ua.includes('Windows')) {
105
+ metadata.os = 'Windows';
106
+ } else if (ua.includes('Mac')) {
107
+ metadata.os = 'macOS';
108
+ } else if (ua.includes('Linux')) {
109
+ metadata.os = 'Linux';
110
+ } else if (ua.includes('Android')) {
111
+ metadata.os = 'Android';
112
+ } else if (ua.includes('iOS')) {
113
+ metadata.os = 'iOS';
114
+ } else {
115
+ metadata.os = 'Unknown';
116
+ }
117
+
118
+ // Timezone
119
+ metadata.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
120
+ metadata.timezoneOffset = new Date().getTimezoneOffset().toString();
121
+
122
+ // Page info
123
+ metadata.url = window.location.href;
124
+ metadata.referrer = document.referrer || 'direct';
125
+
126
+ return metadata;
127
+ }
128
+
129
+ /**
130
+ * Creates a CreateTraceRequest with optional attributes and client metadata
131
+ * @param name name of the trace
132
+ * @param tags optional tags for trace
133
+ * @param attr optional attributes for trace
134
+ * @param includeClientMeta optional flag to include client metadata (ip, browser, os, etc)
135
+ * @returns a CreateTraceRequest object to be used for the createTrace request
136
+ */
137
+ async createTraceRequest({name, tags, attr, includeClientMeta = false}: {name: string, tags?: string[], attr?: { [key: string]: string }, includeClientMeta?: boolean}): Promise<CreateTraceRequest> {
138
+ const createTraceReq = new CreateTraceRequest();
139
+ createTraceReq.setName(name);
140
+ if (tags) {
141
+ createTraceReq.setTagsList(tags);
142
+ }
143
+ const traceAttrs = createTraceReq.getAttributesMap();
144
+
145
+ if (attr) {
146
+ Object.entries(attr).forEach(([key, value]) => {
147
+ if (key.includes('client.')) {
148
+ console.warn(`Attribute key "${key}" is reserved for client metadata. It will be prefixed with "custom."`);
149
+ } else {
150
+ traceAttrs.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
151
+ }
152
+ });
153
+ }
154
+ if (includeClientMeta) {
155
+ const clientMetadata = await this.getClientMetadata();
156
+ Object.entries(clientMetadata).forEach(([key, value]) => {
157
+ traceAttrs.set(`client.${key}`, value);
158
+ });
159
+ }
160
+ return createTraceReq;
161
+ }
162
+
163
+
41
164
  /**
42
165
  * Create a new trace
43
166
  * @param params Parameters to create a new trace
@@ -52,6 +175,46 @@ class ParallaxClient {
52
175
  }
53
176
  }
54
177
 
178
+ /**
179
+ * Create a StartSpanRequest with optional attributes and client metadata
180
+ * @param traceId trace id to associate the span with
181
+ * @param name name of the span
182
+ * @param parentSpanId (optional) create a span off a parent span id
183
+ * @param attr (optional) attributes to add to the span
184
+ * @param includeClientMeta (optional) flag to include client metadata (ip, browser, os, etc)
185
+ * @returns
186
+ */
187
+ async createStartSpanRequest({ traceId, name, parentSpanId, attr, includeClientMeta = false}: {traceId: string, name: string, parentSpanId?: string, attr?: { [key: string]: string }, includeClientMeta?: boolean}): Promise<StartSpanRequest> {
188
+ const startSpanReq = new StartSpanRequest();
189
+ startSpanReq.setTraceId(traceId);
190
+ startSpanReq.setName(name);
191
+ if (parentSpanId) {
192
+ startSpanReq.setParentSpanId(parentSpanId);
193
+ }
194
+ const spanAttrs = startSpanReq.getAttributesMap();
195
+
196
+ if (attr) {
197
+ Object.entries(attr).forEach(([key, value]) => {
198
+ if (key.includes('client.')) {
199
+ console.warn(`Attribute key "${key}" is reserved for client metadata. It will be prefixed with "custom."`);
200
+ } else {
201
+ spanAttrs.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
202
+ }
203
+ });
204
+ }
205
+ try {
206
+ if (includeClientMeta) {
207
+ const clientMetadata = await this.getClientMetadata();
208
+ Object.entries(clientMetadata).forEach(([key, value]) => {
209
+ spanAttrs.set(`client.${key}`, value);
210
+ });
211
+ }
212
+ } catch (error) {
213
+ console.error('Error gathering client metadata for span:', error);
214
+ }
215
+ return startSpanReq;
216
+ }
217
+
55
218
  /**
56
219
  * Start a new span within a trace
57
220
  * @param params Parameters to start a new span
@@ -65,6 +228,26 @@ class ParallaxClient {
65
228
  }
66
229
  }
67
230
 
231
+ /**
232
+ * Create a FinishSpanRequest
233
+ * @param params Parameters to finish a span - traceId, spanId, status (optional) (success, errorMessage)
234
+ * @returns FinishSpanRequest
235
+ */
236
+ createFinishSpanRequest({ traceId, spanId, status }: { traceId: string, spanId: string, status?: { success: boolean, errorMessage: string } }): FinishSpanRequest {
237
+ const request = new FinishSpanRequest();
238
+ request.setTraceId(traceId);
239
+ request.setSpanId(spanId);
240
+ if (status !== undefined) {
241
+ const spanStatus = new FinishSpanRequest.SpanStatus();
242
+ spanStatus.setCode(status.success ? FinishSpanRequest.SpanStatus.StatusCode.STATUS_CODE_OK : FinishSpanRequest.SpanStatus.StatusCode.STATUS_CODE_ERROR);
243
+ if (status.errorMessage) {
244
+ spanStatus.setMessage(status.errorMessage);
245
+ }
246
+ request.setStatus(spanStatus);
247
+ }
248
+ return request;
249
+ }
250
+
68
251
  /**
69
252
  * Finish a span within a trace
70
253
  * @param params Parameters to finish a span
@@ -78,6 +261,26 @@ class ParallaxClient {
78
261
  }
79
262
  }
80
263
 
264
+ /**
265
+ * Creates the add span event request
266
+ * @param params - Parameters to create an AddSpanEventRequest - traceId, spanId, eventName, attr (optional)
267
+ * @returns AddSpanEventRequest
268
+ */
269
+ createAddSpanEventRequest({ traceId, spanId, eventName, attr }: { traceId: string, spanId: string, eventName: string, attr?: { [key: string]: string } }): AddSpanEventRequest {
270
+ const request = new AddSpanEventRequest();
271
+ request.setTraceId(traceId);
272
+ request.setSpanId(spanId);
273
+ request.setEventName(eventName);
274
+ const eventAttrs = request.getAttributesMap();
275
+ if (attr) {
276
+ Object.entries(attr).forEach(([key, value]) => {
277
+ eventAttrs.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
278
+ });
279
+ }
280
+ eventAttrs.set('timestamp', new Date().toISOString());
281
+ return request;
282
+ }
283
+
81
284
  /**
82
285
  * Add an event to a span
83
286
  * @param params Parameters to add an event to a span
@@ -91,6 +294,25 @@ class ParallaxClient {
91
294
  }
92
295
  }
93
296
 
297
+ /**
298
+ * Creates the add span error request
299
+ * @param params - params used to generate the error request ( traceid, span id, error message, error type, stack trace)
300
+ * @returns AddSpanErrorRequest
301
+ */
302
+ createAddSpanErrorRequest({ traceId, spanId, errorMessage, errorType, stackTrace }: { traceId: string, spanId: string, errorMessage: string, errorType?: string, stackTrace?: string }): AddSpanErrorRequest {
303
+ const request = new AddSpanErrorRequest();
304
+ request.setTraceId(traceId);
305
+ request.setSpanId(spanId);
306
+ request.setMessage(errorMessage);
307
+ if (errorType) {
308
+ request.setErrorType(errorType);
309
+ }
310
+ if (stackTrace) {
311
+ request.setStackTrace(stackTrace);
312
+ }
313
+ return request;
314
+ }
315
+
94
316
  /**
95
317
  * Add an error to a span
96
318
  * @param params Parameters to add an error to a span
@@ -104,6 +326,25 @@ class ParallaxClient {
104
326
  }
105
327
  }
106
328
 
329
+ /**
330
+ * Creates the add span hint request
331
+ * @param params - params used to generate the span hint (trace id, parentSpanId, txHash and chainId)
332
+ * @returns AddSpanHintRequest
333
+ */
334
+ createAddSpanHintRequest({ traceId, parentSpanId, txHash, chainId }: { traceId: string, parentSpanId: string, txHash?: string, chainId?: number }): AddSpanHintRequest {
335
+ const hintReq = new AddSpanHintRequest();
336
+ hintReq.setTraceId(traceId);
337
+ hintReq.setParentSpanId(parentSpanId);
338
+
339
+ if (txHash && chainId !== undefined) {
340
+ const chainTxReq = new AddSpanHintRequest.ChainTransaction();
341
+ chainTxReq.setTxHash(txHash);
342
+ chainTxReq.setChainId(chainId);
343
+ hintReq.setChainTransaction(chainTxReq);
344
+ }
345
+ return hintReq;
346
+ }
347
+
107
348
  /**
108
349
  * Add a hint to a span
109
350
  * @param params Parameters to add a hint to a span