@miradorlabs/parallax-web 1.0.8 → 2.0.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 +15 -0
- package/.github/dependabot.yml +39 -0
- package/.github/workflows/pr.yml +33 -0
- package/README.md +145 -280
- package/dist/index.d.ts +56 -214
- package/dist/index.esm.js +760 -1049
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +764 -1054
- package/dist/index.umd.js.map +1 -1
- package/index.ts +2 -5
- package/package.json +4 -2
- package/scripts/release.sh +109 -0
- package/src/index.ts +2 -0
- package/src/parallax/client.ts +66 -0
- package/src/parallax/index.ts +22 -372
- package/src/parallax/metadata.ts +173 -0
- package/src/parallax/trace.ts +209 -0
- package/src/parallax/types.ts +73 -0
- package/tests/parallax.test.ts +395 -372
- package/src/grpc/index.ts +0 -155
- package/src/helpers/index.ts +0 -13
- package/src/parallax/ParallaxService.ts +0 -296
|
@@ -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
|
+
|