@oxy.so/telemetry 1.1.1 → 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.
@@ -1,48 +1,95 @@
1
1
  /** @jest-environment node */
2
2
  import { EventEmitter } from 'node:events';
3
- import { createTrafficCollector, instrumentTrafficFetch, trafficMiddleware, trafficType, type TrafficAggregate } from '../collector';
3
+ import {
4
+ createTrafficCollector,
5
+ instrumentTrafficFetch,
6
+ trafficMiddleware,
7
+ trafficType,
8
+ type TrafficAggregate,
9
+ } from '../collector';
4
10
 
5
11
  describe('ecosystem activity collector', () => {
6
12
  it('reports all products independently of dashboard viewers and counts errors too', async () => {
7
13
  const batches: TrafficAggregate[][] = [];
8
- const collector = createTrafficCollector(async events => { batches.push(events); });
14
+ const collector = createTrafficCollector(async (events) => {
15
+ batches.push(events);
16
+ });
9
17
  for (const service of ['website', 'mention', 'mercaria', 'alia', 'kaana']) {
10
18
  const response = new EventEmitter();
11
- trafficMiddleware(collector, service, 'us-west-2')({ path: '/api/files/private-id', headers: { 'cf-ray': 'example-NRT', authorization: 'secret' } }, response, () => {});
19
+ trafficMiddleware(collector, service, 'us-west-2')(
20
+ {
21
+ path: '/api/files/private-id',
22
+ headers: { 'cf-ray': 'example-NRT', authorization: 'secret' },
23
+ },
24
+ response,
25
+ () => {},
26
+ );
12
27
  response.emit('finish');
13
28
  response.emit('close');
14
29
  }
15
30
  await collector.flush();
16
31
  expect(batches[0]).toHaveLength(10);
17
- expect(batches[0].filter(event => event.direction === 'inbound')).toHaveLength(5);
18
- expect(batches[0].every(event => event.activityType === 'media')).toBe(true);
32
+ expect(batches[0].filter((event) => event.direction === 'inbound')).toHaveLength(5);
33
+ expect(batches[0].every((event) => event.activityType === 'media')).toBe(true);
19
34
  expect(JSON.stringify(batches)).not.toMatch(/private-id|secret|authorization/);
20
35
  });
21
36
 
22
37
  it('keeps scope, direction and category independent for co-located internal services', async () => {
23
38
  const batches: TrafficAggregate[][] = [];
24
- const collector = createTrafficCollector(async events => { batches.push(events); });
39
+ const collector = createTrafficCollector(async (events) => {
40
+ batches.push(events);
41
+ });
25
42
  const response = new EventEmitter();
26
- const request = { path: '/v1/chat/completions', headers: { 'x-oxy-source-region': 'us-west-2' }, serviceApp: undefined as undefined | { appName: string } };
27
- trafficMiddleware(collector, 'kaana', 'us-west-2')(request, response, () => { request.serviceApp = { appName: 'oxy-api' }; });
43
+ const request = {
44
+ path: '/v1/chat/completions',
45
+ headers: { 'x-oxy-source-region': 'us-west-2' },
46
+ serviceApp: undefined as undefined | { appName: string },
47
+ };
48
+ trafficMiddleware(collector, 'kaana', 'us-west-2')(request, response, () => {
49
+ request.serviceApp = { appName: 'oxy-api' };
50
+ });
28
51
  response.emit('finish');
29
52
  await collector.flush();
30
53
  expect(batches[0]).toEqual([
31
- expect.objectContaining({ sourceRegion: 'us-west-2', targetRegion: 'us-west-2', sourceService: 'oxy-api', targetService: 'kaana', direction: 'inbound', scope: 'internal', activityType: 'ai' }),
32
- expect.objectContaining({ sourceService: 'kaana', targetService: 'oxy-api', direction: 'outbound', scope: 'internal', activityType: 'ai' }),
54
+ expect.objectContaining({
55
+ sourceRegion: 'us-west-2',
56
+ targetRegion: 'us-west-2',
57
+ sourceService: 'oxy-api',
58
+ targetService: 'kaana',
59
+ direction: 'inbound',
60
+ scope: 'internal',
61
+ activityType: 'ai',
62
+ }),
63
+ expect.objectContaining({
64
+ sourceService: 'kaana',
65
+ targetService: 'oxy-api',
66
+ direction: 'outbound',
67
+ scope: 'internal',
68
+ activityType: 'ai',
69
+ }),
33
70
  ]);
34
71
  });
35
72
 
36
73
  it('does not fabricate a response on abort and excludes telemetry control traffic', async () => {
37
74
  const batches: TrafficAggregate[][] = [];
38
- const collector = createTrafficCollector(async events => { batches.push(events); });
75
+ const collector = createTrafficCollector(async (events) => {
76
+ batches.push(events);
77
+ });
39
78
  for (const path of ['/health', '/api/ready', '/platform-stats/stream', '/internal/activity']) {
40
79
  const response = new EventEmitter();
41
- trafficMiddleware(collector, 'website', 'us-west-2')({ path, headers: {} }, response, () => {});
80
+ trafficMiddleware(collector, 'website', 'us-west-2')(
81
+ { path, headers: {} },
82
+ response,
83
+ () => {},
84
+ );
42
85
  response.emit('finish');
43
86
  }
44
87
  const aborted = new EventEmitter();
45
- trafficMiddleware(collector, 'mention', 'us-west-2')({ path: '/messages', headers: {} }, aborted, () => {});
88
+ trafficMiddleware(collector, 'mention', 'us-west-2')(
89
+ { path: '/messages', headers: {} },
90
+ aborted,
91
+ () => {},
92
+ );
46
93
  aborted.emit('close');
47
94
  await collector.flush();
48
95
  expect(batches[0]).toHaveLength(1);
@@ -51,41 +98,99 @@ describe('ecosystem activity collector', () => {
51
98
 
52
99
  it('tracks real outgoing calls and only reports the response when one arrived', async () => {
53
100
  const batches: TrafficAggregate[][] = [];
54
- const collector = createTrafficCollector(async events => { batches.push(events); });
55
- const fetcher = instrumentTrafficFetch(jest.fn(async () => new Response('ok', { headers: { 'X-Oxy-Region': 'eu-central-1' } })) as unknown as typeof fetch, collector, 'alia', 'us-west-2', url => url.hostname === 'api.oxy.so' ? { service: 'oxy-api', region: 'us-west-2' } : undefined);
101
+ const collector = createTrafficCollector(async (events) => {
102
+ batches.push(events);
103
+ });
104
+ const fetcher = instrumentTrafficFetch(
105
+ jest.fn(
106
+ async () => new Response('ok', { headers: { 'X-Oxy-Region': 'eu-central-1' } }),
107
+ ) as unknown as typeof fetch,
108
+ collector,
109
+ 'alia',
110
+ 'us-west-2',
111
+ (url) =>
112
+ url.hostname === 'api.oxy.so' ? { service: 'oxy-api', region: 'us-west-2' } : undefined,
113
+ );
56
114
  await fetcher('https://api.oxy.so/v1/chat/completions');
57
115
  await collector.flush();
58
- expect(batches[0].map(event => event.direction)).toEqual(['outbound', 'inbound']);
59
- expect(batches[0].every(event => event.scope === 'internal')).toBe(true);
116
+ expect(batches[0].map((event) => event.direction)).toEqual(['outbound', 'inbound']);
117
+ expect(batches[0].every((event) => event.scope === 'internal')).toBe(true);
60
118
  expect(batches[0][0].targetRegion).toBe('eu-central-1');
61
119
  expect(batches[0][1].sourceRegion).toBe('eu-central-1');
62
- const failed = instrumentTrafficFetch(jest.fn(async () => { throw new Error('offline'); }) as unknown as typeof fetch, collector, 'alia', 'us-west-2', () => undefined);
120
+ const failed = instrumentTrafficFetch(
121
+ jest.fn(async () => {
122
+ throw new Error('offline');
123
+ }) as unknown as typeof fetch,
124
+ collector,
125
+ 'alia',
126
+ 'us-west-2',
127
+ () => undefined,
128
+ );
63
129
  await expect(failed('https://example.com/files')).rejects.toThrow('offline');
64
130
  await collector.flush();
65
131
  expect(batches[1]).toHaveLength(1);
66
- expect(batches[1][0]).toMatchObject({ scope: 'external', direction: 'outbound', activityType: 'media' });
132
+ expect(batches[1][0]).toMatchObject({
133
+ scope: 'external',
134
+ direction: 'outbound',
135
+ activityType: 'media',
136
+ });
67
137
  });
68
138
 
69
139
  it('chunks a busy fleet into ingestible batches and preserves fetch extensions', async () => {
70
140
  const batches: TrafficAggregate[][] = [];
71
- const collector = createTrafficCollector(async events => { batches.push(events); });
72
- for (let index = 0; index < 300; index++) collector.record({ region: 'us-west-2', service: `service-${index}`, scope: 'internal', direction: 'inbound', activityType: 'platform' });
141
+ const collector = createTrafficCollector(async (events) => {
142
+ batches.push(events);
143
+ });
144
+ for (let index = 0; index < 300; index++)
145
+ collector.record({
146
+ region: 'us-west-2',
147
+ service: `service-${index}`,
148
+ scope: 'internal',
149
+ direction: 'inbound',
150
+ activityType: 'platform',
151
+ });
73
152
  await collector.flush();
74
- expect(batches.map(batch => batch.length)).toEqual([256, 44]);
153
+ expect(batches.map((batch) => batch.length)).toEqual([256, 44]);
75
154
  const preconnect = jest.fn();
76
- const baseFetch = Object.assign(jest.fn(async () => new Response()), { preconnect }) as unknown as typeof fetch;
77
- const wrapped = instrumentTrafficFetch(baseFetch, collector, 'website', 'us-west-2', () => undefined);
155
+ const baseFetch = Object.assign(
156
+ jest.fn(async () => new Response()),
157
+ { preconnect },
158
+ ) as unknown as typeof fetch;
159
+ const wrapped = instrumentTrafficFetch(
160
+ baseFetch,
161
+ collector,
162
+ 'website',
163
+ 'us-west-2',
164
+ () => undefined,
165
+ );
78
166
  expect((wrapped as typeof baseFetch & { preconnect: unknown }).preconnect).toBe(preconnect);
79
167
  });
80
168
 
81
169
  it('keeps verified internal attribution when a request aborts', async () => {
82
170
  const batches: TrafficAggregate[][] = [];
83
- const collector = createTrafficCollector(async events => { batches.push(events); });
171
+ const collector = createTrafficCollector(async (events) => {
172
+ batches.push(events);
173
+ });
84
174
  const response = new EventEmitter();
85
- trafficMiddleware(collector, 'oxy-api', 'us-west-2')({ path: '/messages', headers: { 'x-oxy-source-region': 'eu-west-1' }, serviceApp: { appName: 'mention' } }, response, () => {});
175
+ trafficMiddleware(collector, 'oxy-api', 'us-west-2')(
176
+ {
177
+ path: '/messages',
178
+ headers: { 'x-oxy-source-region': 'eu-west-1' },
179
+ serviceApp: { appName: 'mention' },
180
+ },
181
+ response,
182
+ () => {},
183
+ );
86
184
  response.emit('close');
87
185
  await collector.flush();
88
- expect(batches[0]).toEqual([expect.objectContaining({ scope: 'internal', direction: 'inbound', sourceRegion: 'eu-west-1', sourceService: 'mention' })]);
186
+ expect(batches[0]).toEqual([
187
+ expect.objectContaining({
188
+ scope: 'internal',
189
+ direction: 'inbound',
190
+ sourceRegion: 'eu-west-1',
191
+ sourceService: 'mention',
192
+ }),
193
+ ]);
89
194
  });
90
195
 
91
196
  it('classifies versioned media and inference endpoints without retaining the path', () => {
@@ -10,11 +10,13 @@ import {
10
10
 
11
11
  describe('server telemetry primitives', () => {
12
12
  it('prefers a validated forwarded PoP and accepts Cloudflare ray fallback', () => {
13
- expect(metadataFromHeaders({
14
- 'x-oxy-edge-region': 'MAD',
15
- 'cf-ray': 'deadbeef-CDG',
16
- 'x-oxy-activity-id': 'anonymous-runtime-a1',
17
- })).toEqual({ edgePop: 'mad', activityId: 'anonymous-runtime-a1' });
13
+ expect(
14
+ metadataFromHeaders({
15
+ 'x-oxy-edge-region': 'MAD',
16
+ 'cf-ray': 'deadbeef-CDG',
17
+ 'x-oxy-activity-id': 'anonymous-runtime-a1',
18
+ }),
19
+ ).toEqual({ edgePop: 'mad', activityId: 'anonymous-runtime-a1' });
18
20
  expect(metadataFromHeaders({ 'cf-ray': 'deadbeef-CDG' })).toEqual({ edgePop: 'cdg' });
19
21
  });
20
22
 
@@ -8,22 +8,42 @@ class TestSocket extends EventEmitter {
8
8
  nsp = { name: '/messages' };
9
9
  incoming = new Set<(event: string) => void>();
10
10
  outgoing = new Set<(event: string) => void>();
11
- onAny(listener: (event: string) => void) { this.incoming.add(listener); }
12
- onAnyOutgoing(listener: (event: string) => void) { this.outgoing.add(listener); }
13
- offAny(listener: (event: string) => void) { this.incoming.delete(listener); }
14
- offAnyOutgoing(listener: (event: string) => void) { this.outgoing.delete(listener); }
11
+ onAny(listener: (event: string) => void) {
12
+ this.incoming.add(listener);
13
+ }
14
+ onAnyOutgoing(listener: (event: string) => void) {
15
+ this.outgoing.add(listener);
16
+ }
17
+ offAny(listener: (event: string) => void) {
18
+ this.incoming.delete(listener);
19
+ }
20
+ offAnyOutgoing(listener: (event: string) => void) {
21
+ this.outgoing.delete(listener);
22
+ }
15
23
  }
16
24
 
17
25
  describe('socket traffic', () => {
18
26
  it('records real application directions and strips event identifiers and payloads', () => {
19
27
  const socket = new TestSocket();
20
28
  const events: TrafficFlow[] = [];
21
- observeTrafficSocket(socket, { record: event => events.push(event) }, 'syra', 'us-west-2');
22
- socket.incoming.forEach(listener => listener('player:private-track-id'));
23
- socket.outgoing.forEach(listener => listener('message:private-user-id'));
29
+ observeTrafficSocket(socket, { record: (event) => events.push(event) }, 'syra', 'us-west-2');
30
+ socket.incoming.forEach((listener) => listener('player:private-track-id'));
31
+ socket.outgoing.forEach((listener) => listener('message:private-user-id'));
24
32
  expect(events).toEqual([
25
- expect.objectContaining({ scope: 'external', direction: 'inbound', sourceRegion: 'edge-mad', targetRegion: 'us-west-2', activityType: 'media' }),
26
- expect.objectContaining({ scope: 'external', direction: 'outbound', sourceRegion: 'us-west-2', targetRegion: 'edge-mad', activityType: 'communication' }),
33
+ expect.objectContaining({
34
+ scope: 'external',
35
+ direction: 'inbound',
36
+ sourceRegion: 'edge-mad',
37
+ targetRegion: 'us-west-2',
38
+ activityType: 'media',
39
+ }),
40
+ expect.objectContaining({
41
+ scope: 'external',
42
+ direction: 'outbound',
43
+ sourceRegion: 'us-west-2',
44
+ targetRegion: 'edge-mad',
45
+ activityType: 'communication',
46
+ }),
27
47
  ]);
28
48
  expect(JSON.stringify(events)).not.toMatch(/private|authorization|track-id|user-id/);
29
49
  socket.emit('disconnect');
@@ -34,11 +54,23 @@ describe('socket traffic', () => {
34
54
  const socket = new TestSocket();
35
55
  const events: TrafficFlow[] = [];
36
56
  let peer: { service: string; region: string } | undefined;
37
- const stop = observeTrafficSocket(socket, { record: event => events.push(event) }, 'alia', 'us-west-2', { getPeer: () => peer });
57
+ const stop = observeTrafficSocket(
58
+ socket,
59
+ { record: (event) => events.push(event) },
60
+ 'alia',
61
+ 'us-west-2',
62
+ { getPeer: () => peer },
63
+ );
38
64
  peer = { service: 'mention', region: 'eu-west-1' };
39
- socket.incoming.forEach(listener => listener('agent:update'));
40
- expect(events[0]).toMatchObject({ scope: 'internal', sourceService: 'mention', sourceRegion: 'eu-west-1', activityType: 'ai' });
41
- stop(); stop();
65
+ socket.incoming.forEach((listener) => listener('agent:update'));
66
+ expect(events[0]).toMatchObject({
67
+ scope: 'internal',
68
+ sourceService: 'mention',
69
+ sourceRegion: 'eu-west-1',
70
+ activityType: 'ai',
71
+ });
72
+ stop();
73
+ stop();
42
74
  expect(socket.listenerCount('disconnect')).toBe(0);
43
75
  });
44
76
 
@@ -61,32 +93,39 @@ describe('real Socket.IO transport', () => {
61
93
  const server = createServer();
62
94
  const sockets = new Server(server);
63
95
  const events: TrafficFlow[] = [];
64
- sockets.on('connection', socket => {
65
- observeTrafficSocket(socket, { record: flow => events.push(flow) }, 'syra', 'us-west-2');
96
+ sockets.on('connection', (socket) => {
97
+ observeTrafficSocket(socket, { record: (flow) => events.push(flow) }, 'syra', 'us-west-2');
66
98
  socket.on('player:seek', (_position, acknowledge) => {
67
99
  acknowledge('ok');
68
100
  sockets.emit('player:state', { privateTrackId: 'never-published' });
69
101
  });
70
102
  });
71
- await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
103
+ await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
72
104
  const address = server.address();
73
105
  if (!address || typeof address === 'string') throw new Error('Missing listener');
74
- const client = io(`http://127.0.0.1:${address.port}`, { transports: ['websocket'], extraHeaders: { 'X-Oxy-Edge-Region': 'NRT' } });
106
+ const client = io(`http://127.0.0.1:${address.port}`, {
107
+ transports: ['websocket'],
108
+ extraHeaders: { 'X-Oxy-Edge-Region': 'NRT' },
109
+ });
75
110
  try {
76
- await new Promise<void>((resolve, reject) => { client.once('connect', resolve); client.once('connect_error', reject); });
77
- const received = new Promise<void>(resolve => client.once('player:state', () => resolve()));
111
+ await new Promise<void>((resolve, reject) => {
112
+ client.once('connect', resolve);
113
+ client.once('connect_error', reject);
114
+ });
115
+ const received = new Promise<void>((resolve) => client.once('player:state', () => resolve()));
78
116
  await client.emitWithAck('player:seek', 42);
79
117
  await received;
80
118
  expect(events).toHaveLength(2);
81
- expect(events.map(flow => [flow.direction, flow.scope, flow.activityType])).toEqual([
82
- ['inbound', 'external', 'media'], ['outbound', 'external', 'media'],
119
+ expect(events.map((flow) => [flow.direction, flow.scope, flow.activityType])).toEqual([
120
+ ['inbound', 'external', 'media'],
121
+ ['outbound', 'external', 'media'],
83
122
  ]);
84
123
  expect(events[0].sourceRegion).toBe('edge-nrt');
85
124
  expect(events[1].targetRegion).toBe('edge-nrt');
86
125
  expect(JSON.stringify(events)).not.toContain('never-published');
87
126
  } finally {
88
127
  client.disconnect();
89
- await new Promise<void>(resolve => sockets.close(() => resolve()));
128
+ await new Promise<void>((resolve) => sockets.close(() => resolve()));
90
129
  }
91
130
  });
92
131
  });
@@ -97,21 +136,33 @@ describe('raw WebSocket transport', () => {
97
136
  const events: TrafficFlow[] = [];
98
137
  const original = jest.fn((_data: unknown, callback: (error?: Error) => void) => callback());
99
138
  const socket = Object.assign(new EventEmitter(), { readyState: 1, send: original });
100
- const stop = observeTrafficWebSocket(socket, { record: flow => events.push(flow) }, 'alia', 'us-west-2', {
101
- activityType: 'ai', getPeer: () => ({ service: 'alia-integrations', region: 'eu-west-1' }),
102
- });
139
+ const stop = observeTrafficWebSocket(
140
+ socket,
141
+ { record: (flow) => events.push(flow) },
142
+ 'alia',
143
+ 'us-west-2',
144
+ {
145
+ activityType: 'ai',
146
+ getPeer: () => ({ service: 'alia-integrations', region: 'eu-west-1' }),
147
+ },
148
+ );
103
149
  const callback = jest.fn();
104
150
  socket.send({ privateBody: 'not-recorded' }, callback);
105
151
  socket.emit('message', Buffer.from('private-message'));
106
152
  expect(original).toHaveBeenCalledWith({ privateBody: 'not-recorded' }, callback);
107
153
  expect(callback).toHaveBeenCalledTimes(1);
108
- expect(events.map(flow => flow.direction)).toEqual(['outbound', 'inbound']);
109
- expect(events.every(flow => flow.scope === 'internal' && flow.activityType === 'ai')).toBe(true);
154
+ expect(events.map((flow) => flow.direction)).toEqual(['outbound', 'inbound']);
155
+ expect(events.every((flow) => flow.scope === 'internal' && flow.activityType === 'ai')).toBe(
156
+ true,
157
+ );
110
158
  expect(JSON.stringify(events)).not.toMatch(/private|not-recorded/);
111
- original.mockImplementationOnce(() => { throw new Error('send failed'); });
159
+ original.mockImplementationOnce(() => {
160
+ throw new Error('send failed');
161
+ });
112
162
  expect(() => socket.send('', callback)).toThrow('send failed');
113
163
  expect(events).toHaveLength(2);
114
- stop(); stop();
164
+ stop();
165
+ stop();
115
166
  expect(socket.send).toBe(original);
116
167
  expect(socket.listenerCount('message')).toBe(0);
117
168
  });
package/src/browser.ts CHANGED
@@ -3,6 +3,12 @@ import { OXY_ACTIVITY_ID_HEADER, OXY_EDGE_REGION_HEADER } from './index.js';
3
3
  export const DEFAULT_ACTIVITY_ID_ROTATION_MS = 5 * 60 * 1_000;
4
4
  export const DEFAULT_EDGE_TRACE_TIMEOUT_MS = 1_000;
5
5
  export const DEFAULT_EDGE_CACHE_MS = 15_000;
6
+ /**
7
+ * How long `peekEdgeRegionHeader` trusts a known PoP before refreshing it in the
8
+ * background. A client's serving PoP rarely changes within minutes, and the
9
+ * peek never blocks, so this only bounds the trace traffic.
10
+ */
11
+ export const DEFAULT_EDGE_PEEK_REFRESH_MS = 10 * 60_000;
6
12
 
7
13
  interface TraceResponse {
8
14
  ok: boolean;
@@ -13,6 +19,7 @@ export interface BrowserTelemetryOptions {
13
19
  activityIdRotationMs?: number;
14
20
  edgeTraceTimeoutMs?: number;
15
21
  edgeCacheMs?: number;
22
+ edgePeekRefreshMs?: number;
16
23
  now?: () => number;
17
24
  crypto?: Pick<Crypto, 'getRandomValues' | 'randomUUID'>;
18
25
  location?: Pick<Location, 'hostname' | 'protocol'>;
@@ -22,10 +29,18 @@ export interface BrowserTelemetryOptions {
22
29
  export interface BrowserTelemetry {
23
30
  getActivityIdHeader(): Record<string, string>;
24
31
  getEdgeRegionHeader(): Promise<Record<string, string>>;
32
+ /**
33
+ * The last known edge-region header, synchronously, never waiting on the
34
+ * trace: `{}` until the first discovery lands. A stale or missing value
35
+ * starts a refresh in the background.
36
+ */
37
+ peekEdgeRegionHeader(): Record<string, string>;
25
38
  getHeaders(): Promise<Record<string, string>>;
26
39
  }
27
40
 
28
- function createUuid(cryptoApi: Pick<Crypto, 'getRandomValues' | 'randomUUID'> | undefined): string | null {
41
+ function createUuid(
42
+ cryptoApi: Pick<Crypto, 'getRandomValues' | 'randomUUID'> | undefined,
43
+ ): string | null {
29
44
  if (!cryptoApi) return null;
30
45
  if (typeof cryptoApi.randomUUID === 'function') return cryptoApi.randomUUID();
31
46
 
@@ -38,9 +53,11 @@ function createUuid(cryptoApi: Pick<Crypto, 'getRandomValues' | 'randomUUID'> |
38
53
 
39
54
  function eligibleLocation(location: Pick<Location, 'hostname' | 'protocol'> | undefined): boolean {
40
55
  if (!location || (location.protocol !== 'https:' && location.protocol !== 'http:')) return false;
41
- return location.hostname !== 'localhost'
42
- && location.hostname !== '127.0.0.1'
43
- && location.hostname !== '[::1]';
56
+ return (
57
+ location.hostname !== 'localhost' &&
58
+ location.hostname !== '127.0.0.1' &&
59
+ location.hostname !== '[::1]'
60
+ );
44
61
  }
45
62
 
46
63
  export function createBrowserTelemetry(options: BrowserTelemetryOptions = {}): BrowserTelemetry {
@@ -54,7 +71,11 @@ export function createBrowserTelemetry(options: BrowserTelemetryOptions = {}): B
54
71
  const hasBrowserRuntime = typeof window !== 'undefined' && typeof document !== 'undefined';
55
72
  if (!hasInjectedBrowser && !hasBrowserRuntime) return {};
56
73
  const now = (options.now ?? Date.now)();
57
- if (!activityId || now < activityIdCreatedAt || now - activityIdCreatedAt >= (options.activityIdRotationMs ?? DEFAULT_ACTIVITY_ID_ROTATION_MS)) {
74
+ if (
75
+ !activityId ||
76
+ now < activityIdCreatedAt ||
77
+ now - activityIdCreatedAt >= (options.activityIdRotationMs ?? DEFAULT_ACTIVITY_ID_ROTATION_MS)
78
+ ) {
58
79
  activityId = createUuid(options.crypto ?? globalThis.crypto);
59
80
  if (!activityId) return {};
60
81
  activityIdCreatedAt = now;
@@ -63,14 +84,22 @@ export function createBrowserTelemetry(options: BrowserTelemetryOptions = {}): B
63
84
  };
64
85
 
65
86
  const discoverEdgePop = async (): Promise<string | null> => {
66
- const browserLocation = options.location ?? (typeof window === 'undefined' ? undefined : window.location);
87
+ const browserLocation =
88
+ options.location ?? (typeof window === 'undefined' ? undefined : window.location);
67
89
  if (!eligibleLocation(browserLocation)) return null;
68
90
  const controller = new AbortController();
69
- const timeout = setTimeout(() => controller.abort(), options.edgeTraceTimeoutMs ?? DEFAULT_EDGE_TRACE_TIMEOUT_MS);
91
+ const timeout = setTimeout(
92
+ () => controller.abort(),
93
+ options.edgeTraceTimeoutMs ?? DEFAULT_EDGE_TRACE_TIMEOUT_MS,
94
+ );
70
95
  try {
71
96
  const response = await (options.fetchTrace
72
97
  ? options.fetchTrace(controller.signal)
73
- : fetch('/cdn-cgi/trace', { cache: 'no-store', credentials: 'omit', signal: controller.signal }));
98
+ : fetch('/cdn-cgi/trace', {
99
+ cache: 'no-store',
100
+ credentials: 'omit',
101
+ signal: controller.signal,
102
+ }));
74
103
  if (!response.ok) return null;
75
104
  const match = (await response.text()).match(/^colo=([a-z]{3})$/im);
76
105
  return match ? match[1].toLowerCase() : null;
@@ -83,7 +112,11 @@ export function createBrowserTelemetry(options: BrowserTelemetryOptions = {}): B
83
112
 
84
113
  const getEdgeRegionHeader = async (): Promise<Record<string, string>> => {
85
114
  const now = (options.now ?? Date.now)();
86
- if (!edgePopPromise || now < edgePopReadAt || now - edgePopReadAt >= (options.edgeCacheMs ?? DEFAULT_EDGE_CACHE_MS)) {
115
+ if (
116
+ !edgePopPromise ||
117
+ now < edgePopReadAt ||
118
+ now - edgePopReadAt >= (options.edgeCacheMs ?? DEFAULT_EDGE_CACHE_MS)
119
+ ) {
87
120
  edgePopReadAt = now;
88
121
  edgePopPromise = discoverEdgePop();
89
122
  }
@@ -91,11 +124,36 @@ export function createBrowserTelemetry(options: BrowserTelemetryOptions = {}): B
91
124
  return edgePop ? { [OXY_EDGE_REGION_HEADER]: edgePop } : {};
92
125
  };
93
126
 
127
+ let peekedPop: string | null = null;
128
+ let peekRefreshAt = -Infinity;
129
+ let peekInFlight = false;
130
+
131
+ const peekEdgeRegionHeader = (): Record<string, string> => {
132
+ const now = (options.now ?? Date.now)();
133
+ if (
134
+ !peekInFlight &&
135
+ (now < peekRefreshAt ||
136
+ now - peekRefreshAt >= (options.edgePeekRefreshMs ?? DEFAULT_EDGE_PEEK_REFRESH_MS))
137
+ ) {
138
+ peekRefreshAt = now;
139
+ peekInFlight = true;
140
+ void discoverEdgePop()
141
+ .then((pop) => {
142
+ if (pop) peekedPop = pop;
143
+ })
144
+ .finally(() => {
145
+ peekInFlight = false;
146
+ });
147
+ }
148
+ return peekedPop ? { [OXY_EDGE_REGION_HEADER]: peekedPop } : {};
149
+ };
150
+
94
151
  return {
95
152
  getActivityIdHeader,
96
153
  getEdgeRegionHeader,
154
+ peekEdgeRegionHeader,
97
155
  async getHeaders(): Promise<Record<string, string>> {
98
- return { ...await getEdgeRegionHeader(), ...getActivityIdHeader() };
156
+ return { ...(await getEdgeRegionHeader()), ...getActivityIdHeader() };
99
157
  },
100
158
  };
101
159
  }
@@ -108,5 +166,8 @@ export const getBrowserActivityIdHeader = (): Record<string, string> =>
108
166
  export const getBrowserEdgeRegionHeader = (): Promise<Record<string, string>> =>
109
167
  defaultBrowserTelemetry.getEdgeRegionHeader();
110
168
 
169
+ export const peekBrowserEdgeRegionHeader = (): Record<string, string> =>
170
+ defaultBrowserTelemetry.peekEdgeRegionHeader();
171
+
111
172
  export const getBrowserTelemetryHeaders = (): Promise<Record<string, string>> =>
112
173
  defaultBrowserTelemetry.getHeaders();