@miradorlabs/parallax-web 1.0.8 → 2.0.1

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.
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Browser metadata collection utilities
3
+ */
4
+
5
+ interface BrowserInfo {
6
+ name: string;
7
+ version: string;
8
+ }
9
+
10
+ interface OSInfo {
11
+ name: string;
12
+ version: string;
13
+ }
14
+
15
+ interface NetworkInformation {
16
+ effectiveType?: string;
17
+ downlink?: number;
18
+ saveData?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Detect browser name and version from user agent
23
+ */
24
+ export function detectBrowser(ua: string): BrowserInfo {
25
+ if (ua.includes('Edg/')) {
26
+ return {
27
+ name: 'Edge',
28
+ version: ua.match(/Edg\/(\d+(\.\d+)?)/)?.[1] || 'unknown',
29
+ };
30
+ }
31
+ if (ua.includes('Chrome/')) {
32
+ return {
33
+ name: 'Chrome',
34
+ version: ua.match(/Chrome\/(\d+(\.\d+)?)/)?.[1] || 'unknown',
35
+ };
36
+ }
37
+ if (ua.includes('Firefox/')) {
38
+ return {
39
+ name: 'Firefox',
40
+ version: ua.match(/Firefox\/(\d+(\.\d+)?)/)?.[1] || 'unknown',
41
+ };
42
+ }
43
+ if (ua.includes('Safari/') && !ua.includes('Chrome')) {
44
+ return {
45
+ name: 'Safari',
46
+ version: ua.match(/Version\/(\d+(\.\d+)?)/)?.[1] || 'unknown',
47
+ };
48
+ }
49
+ return { name: 'Unknown', version: 'unknown' };
50
+ }
51
+
52
+ /**
53
+ * Detect operating system name and version from user agent
54
+ */
55
+ export function detectOS(ua: string): OSInfo {
56
+ if (ua.includes('Windows NT')) {
57
+ const ntVersion = ua.match(/Windows NT (\d+\.\d+)/)?.[1];
58
+ return {
59
+ name: 'Windows',
60
+ version: ntVersion === '10.0' ? '10/11' : ntVersion || 'unknown',
61
+ };
62
+ }
63
+ if (ua.includes('Mac OS X')) {
64
+ return {
65
+ name: 'macOS',
66
+ version: ua.match(/Mac OS X (\d+[._]\d+)/)?.[1]?.replace('_', '.') || 'unknown',
67
+ };
68
+ }
69
+ if (ua.includes('Android')) {
70
+ return {
71
+ name: 'Android',
72
+ version: ua.match(/Android (\d+(\.\d+)?)/)?.[1] || 'unknown',
73
+ };
74
+ }
75
+ if (ua.includes('iPhone') || ua.includes('iPad')) {
76
+ return {
77
+ name: 'iOS',
78
+ version: ua.match(/OS (\d+[._]\d+)/)?.[1]?.replace('_', '.') || 'unknown',
79
+ };
80
+ }
81
+ if (ua.includes('Linux')) {
82
+ return { name: 'Linux', version: 'unknown' };
83
+ }
84
+ return { name: 'Unknown', version: 'unknown' };
85
+ }
86
+
87
+ /**
88
+ * Detect device type from user agent
89
+ */
90
+ export function detectDeviceType(ua: string): 'desktop' | 'mobile' | 'tablet' {
91
+ if (/Mobile|Android|iPhone|iPad|iPod/i.test(ua)) {
92
+ return /iPad|Tablet/i.test(ua) ? 'tablet' : 'mobile';
93
+ }
94
+ return 'desktop';
95
+ }
96
+
97
+ /**
98
+ * Gather client metadata from the browser environment
99
+ * Note: IP address is captured by the backend from request headers
100
+ */
101
+ export function getClientMetadata(): { [key: string]: string } {
102
+ const metadata: { [key: string]: string } = {};
103
+ const ua = navigator.userAgent;
104
+
105
+ // Browser detection
106
+ const browser = detectBrowser(ua);
107
+ metadata.browser = browser.name;
108
+ metadata.browserVersion = browser.version;
109
+
110
+ // OS detection
111
+ const os = detectOS(ua);
112
+ metadata.os = os.name;
113
+ metadata.osVersion = os.version;
114
+
115
+ // Device type
116
+ metadata.deviceType = detectDeviceType(ua);
117
+
118
+ // Core browser info
119
+ metadata.userAgent = ua;
120
+ metadata.language = navigator.language;
121
+ metadata.languages = navigator.languages?.join(',') || navigator.language;
122
+
123
+ // Screen and display
124
+ metadata.screenWidth = window.screen.width.toString();
125
+ metadata.screenHeight = window.screen.height.toString();
126
+ metadata.viewportWidth = window.innerWidth.toString();
127
+ metadata.viewportHeight = window.innerHeight.toString();
128
+ metadata.colorDepth = window.screen.colorDepth.toString();
129
+ metadata.pixelRatio = window.devicePixelRatio?.toString() || '1';
130
+
131
+ // Hardware capabilities
132
+ metadata.cpuCores = navigator.hardwareConcurrency?.toString() || 'unknown';
133
+ const nav = navigator as Navigator & { deviceMemory?: number };
134
+ if (nav.deviceMemory) {
135
+ metadata.deviceMemory = nav.deviceMemory.toString();
136
+ }
137
+
138
+ // Touch support
139
+ metadata.touchSupport = ('ontouchstart' in window || navigator.maxTouchPoints > 0).toString();
140
+ metadata.maxTouchPoints = navigator.maxTouchPoints?.toString() || '0';
141
+
142
+ // Connection info (Network Information API - non-standard)
143
+ const connection = (navigator as Navigator & { connection?: NetworkInformation }).connection;
144
+ if (connection) {
145
+ metadata.connectionType = connection.effectiveType || 'unknown';
146
+ if (connection.downlink) {
147
+ metadata.connectionSpeed = connection.downlink.toString();
148
+ }
149
+ if (connection.saveData !== undefined) {
150
+ metadata.dataSaver = connection.saveData.toString();
151
+ }
152
+ }
153
+
154
+ // Browser state
155
+ metadata.cookiesEnabled = navigator.cookieEnabled.toString();
156
+ metadata.online = navigator.onLine.toString();
157
+ metadata.doNotTrack = navigator.doNotTrack || 'unspecified';
158
+
159
+ // Timezone
160
+ metadata.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
161
+ metadata.timezoneOffset = new Date().getTimezoneOffset().toString();
162
+
163
+ // Page context
164
+ metadata.url = window.location.href;
165
+ metadata.origin = window.location.origin;
166
+ metadata.pathname = window.location.pathname;
167
+ metadata.referrer = document.referrer || 'direct';
168
+
169
+ // Document state
170
+ metadata.documentVisibility = document.visibilityState;
171
+
172
+ return metadata;
173
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * ParallaxTrace builder class for constructing traces with method chaining
3
+ */
4
+ import {
5
+ CreateTraceRequest,
6
+ CreateTraceResponse,
7
+ Event,
8
+ TxHashHint as TxHashHintProto,
9
+ Chain,
10
+ } from 'mirador-gateway-parallax-web/proto/gateway/parallax/v1/parallax_gateway_pb';
11
+ import { ResponseStatus } from 'mirador-gateway-parallax-web/proto/common/v1/status_pb';
12
+ import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
13
+ import type { TraceEvent, TxHashHint, ChainName } from './types';
14
+ import { getClientMetadata } from './metadata';
15
+
16
+ /**
17
+ * Maps chain names to proto Chain enum values
18
+ */
19
+ const CHAIN_MAP: Record<ChainName, Chain> = {
20
+ ethereum: Chain.CHAIN_ETHEREUM,
21
+ polygon: Chain.CHAIN_POLYGON,
22
+ arbitrum: Chain.CHAIN_ARBITRUM,
23
+ base: Chain.CHAIN_BASE,
24
+ optimism: Chain.CHAIN_OPTIMISM,
25
+ bsc: Chain.CHAIN_BSC,
26
+ };
27
+
28
+ /**
29
+ * Interface for the client that ParallaxTrace uses to submit traces
30
+ * @internal
31
+ */
32
+ export interface TraceSubmitter {
33
+ _sendTrace(request: CreateTraceRequest): Promise<CreateTraceResponse>;
34
+ }
35
+
36
+ /**
37
+ * Builder class for constructing traces with method chaining
38
+ * Automatically handles web-specific features like client metadata
39
+ */
40
+ export class ParallaxTrace {
41
+ private name: string;
42
+ private attributes: { [key: string]: string } = {};
43
+ private tags: string[] = [];
44
+ private events: TraceEvent[] = [];
45
+ private txHashHint?: TxHashHint;
46
+ private client: TraceSubmitter;
47
+ private includeClientMeta: boolean;
48
+
49
+ constructor(client: TraceSubmitter, name: string = '', includeClientMeta: boolean = true) {
50
+ this.client = client;
51
+ this.name = name;
52
+ this.includeClientMeta = includeClientMeta;
53
+ }
54
+
55
+ /**
56
+ * Add an attribute to the trace
57
+ * @param key Attribute key
58
+ * @param value Attribute value (objects are stringified, primitives converted to string)
59
+ * @returns This trace builder for chaining
60
+ */
61
+ addAttribute(key: string, value: string | number | boolean | object): this {
62
+ this.attributes[key] =
63
+ typeof value === 'object' && value !== null
64
+ ? JSON.stringify(value)
65
+ : String(value);
66
+ return this;
67
+ }
68
+
69
+ /**
70
+ * Add multiple attributes to the trace
71
+ * @param attributes Object containing key-value pairs (objects are stringified)
72
+ * @returns This trace builder for chaining
73
+ */
74
+ addAttributes(attributes: { [key: string]: string | number | boolean | object }): this {
75
+ for (const [key, value] of Object.entries(attributes)) {
76
+ this.attributes[key] =
77
+ typeof value === 'object' && value !== null
78
+ ? JSON.stringify(value)
79
+ : String(value);
80
+ }
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * Add a tag to the trace
86
+ * @param tag Tag to add
87
+ * @returns This trace builder for chaining
88
+ */
89
+ addTag(tag: string): this {
90
+ this.tags.push(tag);
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Add multiple tags to the trace
96
+ * @param tags Array of tags to add
97
+ * @returns This trace builder for chaining
98
+ */
99
+ addTags(tags: string[]): this {
100
+ this.tags.push(...tags);
101
+ return this;
102
+ }
103
+
104
+ /**
105
+ * Add an event to the trace
106
+ * @param eventName Name of the event
107
+ * @param details Optional details (can be a JSON string or object that will be stringified)
108
+ * @param timestamp Optional timestamp (defaults to current time)
109
+ * @returns This trace builder for chaining
110
+ */
111
+ addEvent(eventName: string, details?: string | object, timestamp?: Date): this {
112
+ const detailsString = typeof details === 'object' && details !== null
113
+ ? JSON.stringify(details)
114
+ : details;
115
+
116
+ this.events.push({
117
+ eventName,
118
+ details: detailsString,
119
+ timestamp: timestamp || new Date(),
120
+ });
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * Set the transaction hash hint for blockchain correlation
126
+ * @param txHash Transaction hash
127
+ * @param chain Chain name (e.g., "ethereum", "polygon", "base")
128
+ * @param details Optional details about the transaction
129
+ * @returns This trace builder for chaining
130
+ */
131
+ setTxHint(txHash: string, chain: ChainName, details?: string): this {
132
+ this.txHashHint = {
133
+ txHash,
134
+ chain,
135
+ details,
136
+ timestamp: new Date(),
137
+ };
138
+ return this;
139
+ }
140
+
141
+ /**
142
+ * Create and submit the trace to the gateway
143
+ * @returns The trace ID if successful, undefined if failed
144
+ */
145
+ async create(): Promise<string | undefined> {
146
+ // Build the CreateTraceRequest
147
+ const request = new CreateTraceRequest();
148
+ request.setName(this.name);
149
+ request.setTagsList(this.tags);
150
+
151
+ // Add attributes
152
+ const attrsMap = request.getAttributesMap();
153
+ for (const [key, value] of Object.entries(this.attributes)) {
154
+ attrsMap.set(key, value);
155
+ }
156
+
157
+ // Add client metadata if requested
158
+ if (this.includeClientMeta) {
159
+ const clientMetadata = getClientMetadata();
160
+ for (const [key, value] of Object.entries(clientMetadata)) {
161
+ attrsMap.set(`client.${key}`, value);
162
+ }
163
+ }
164
+
165
+ // Add events
166
+ const eventsList: Event[] = [];
167
+ for (const event of this.events) {
168
+ const eventMsg = new Event();
169
+ eventMsg.setName(event.eventName);
170
+ if (event.details) {
171
+ eventMsg.setDetails(event.details);
172
+ }
173
+ const timestamp = new Timestamp();
174
+ timestamp.fromDate(event.timestamp);
175
+ eventMsg.setTimestamp(timestamp);
176
+ eventsList.push(eventMsg);
177
+ }
178
+ request.setEventsList(eventsList);
179
+
180
+ // Add transaction hash hint if present
181
+ if (this.txHashHint) {
182
+ const txHint = new TxHashHintProto();
183
+ txHint.setTxHash(this.txHashHint.txHash);
184
+ txHint.setChain(CHAIN_MAP[this.txHashHint.chain]);
185
+ if (this.txHashHint.details) {
186
+ txHint.setDetails(this.txHashHint.details);
187
+ }
188
+ const timestamp = new Timestamp();
189
+ timestamp.fromDate(this.txHashHint.timestamp);
190
+ txHint.setTimestamp(timestamp);
191
+ request.setTxHashHint(txHint);
192
+ }
193
+
194
+ try {
195
+ const response = await this.client._sendTrace(request);
196
+ const responseStatus = response.getStatus();
197
+
198
+ if (responseStatus?.getCode() !== ResponseStatus.StatusCode.STATUS_CODE_SUCCESS) {
199
+ console.log('[ParallaxTrace] Error:', responseStatus?.getErrorMessage() || 'Unknown error');
200
+ return undefined;
201
+ }
202
+
203
+ return response.getTraceId();
204
+ } catch (error) {
205
+ console.log('[ParallaxTrace] Error creating trace:', error);
206
+ return undefined;
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * TypeScript interfaces for the Parallax SDK
3
+ */
4
+
5
+ /**
6
+ * Configuration options for ParallaxClient
7
+ */
8
+ export interface ParallaxClientConfig {
9
+ apiKey: string;
10
+ apiUrl?: string;
11
+ }
12
+
13
+ /**
14
+ * Client metadata collected from the browser environment
15
+ */
16
+ export interface ClientMetadata {
17
+ browser: string;
18
+ browserVersion: string;
19
+ os: string;
20
+ osVersion: string;
21
+ deviceType: 'desktop' | 'mobile' | 'tablet';
22
+ userAgent: string;
23
+ language: string;
24
+ languages: string;
25
+ screenWidth: string;
26
+ screenHeight: string;
27
+ viewportWidth: string;
28
+ viewportHeight: string;
29
+ colorDepth: string;
30
+ pixelRatio: string;
31
+ cpuCores: string;
32
+ deviceMemory?: string;
33
+ touchSupport: string;
34
+ maxTouchPoints: string;
35
+ connectionType?: string;
36
+ connectionSpeed?: string;
37
+ dataSaver?: string;
38
+ cookiesEnabled: string;
39
+ online: string;
40
+ doNotTrack: string;
41
+ timezone: string;
42
+ timezoneOffset: string;
43
+ url: string;
44
+ origin: string;
45
+ pathname: string;
46
+ referrer: string;
47
+ documentVisibility: string;
48
+ }
49
+
50
+ /**
51
+ * An event to be recorded in a trace
52
+ */
53
+ export interface TraceEvent {
54
+ eventName: string;
55
+ details?: string;
56
+ timestamp: Date;
57
+ }
58
+
59
+ /**
60
+ * Supported chain names (maps to Chain enum in proto)
61
+ */
62
+ export type ChainName = 'ethereum' | 'polygon' | 'arbitrum' | 'base' | 'optimism' | 'bsc';
63
+
64
+ /**
65
+ * Transaction hash hint for blockchain correlation
66
+ */
67
+ export interface TxHashHint {
68
+ txHash: string;
69
+ chain: ChainName;
70
+ details?: string;
71
+ timestamp: Date;
72
+ }
73
+