Skip to content

Commit 5640372

Browse files
committed
wip
1 parent fc893c5 commit 5640372

File tree

5 files changed

+132
-21
lines changed

5 files changed

+132
-21
lines changed

packages/browser/src/browser/__tests__/standalone-analytics.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import jsdom, { JSDOM } from 'jsdom'
22
import { InitOptions, getGlobalAnalytics } from '../../'
3-
import { AnalyticsBrowser, loadCDNSettings } from '../../browser'
3+
import { AnalyticsBrowser } from '../../browser'
44
import { snippet } from '../../tester/__fixtures__/segment-snippet'
55
import { install } from '../standalone-analytics'
66
import unfetch from 'unfetch'

packages/browser/src/browser/index.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { ClassicIntegrationSource } from '../plugins/ajs-destination/types'
3131
import { attachInspector } from '../core/inspector'
3232
import { Stats } from '../core/stats'
3333
import { setGlobalAnalyticsKey } from '../lib/global-analytics-helper'
34+
import { loadCDNSettings } from '../core/http-client/api-clients'
35+
import { FetchHTTPClient } from '../core/http-client'
3436

3537
export interface RemoteIntegrationSettings {
3638
/* @deprecated - This does not indicate browser types anymore */
@@ -153,25 +155,6 @@ export interface AnalyticsBrowserSettings {
153155
classicIntegrations?: ClassicIntegrationSource[]
154156
}
155157

156-
export function loadCDNSettings(
157-
writeKey: string,
158-
baseUrl: string
159-
): Promise<CDNSettings> {
160-
return fetch(`${baseUrl}/v1/projects/${writeKey}/settings`)
161-
.then((res) => {
162-
if (!res.ok) {
163-
return res.text().then((errorResponseMessage) => {
164-
throw new Error(errorResponseMessage)
165-
})
166-
}
167-
return res.json()
168-
})
169-
.catch((err) => {
170-
console.error(err.message)
171-
throw err
172-
})
173-
}
174-
175158
function hasLegacyDestinations(settings: CDNSettings): boolean {
176159
return (
177160
getProcessEnv().NODE_ENV !== 'test' &&
@@ -350,6 +333,8 @@ async function loadAnalytics(
350333
return [new NullAnalytics(), Context.system()]
351334
}
352335

336+
const httpClient = new FetchHTTPClient(options.httpClient)
337+
353338
if (options.globalAnalyticsKey)
354339
setGlobalAnalyticsKey(options.globalAnalyticsKey)
355340
// this is an ugly side-effect, but it's for the benefits of the plugins that get their cdn via getCDN()
@@ -362,7 +347,11 @@ async function loadAnalytics(
362347

363348
const cdnURL = settings.cdnURL ?? getCDN()
364349
let cdnSettings =
365-
settings.cdnSettings ?? (await loadCDNSettings(settings.writeKey, cdnURL))
350+
settings.cdnSettings ??
351+
(await loadCDNSettings(httpClient, {
352+
writeKey: settings.writeKey,
353+
baseUrl: cdnURL,
354+
}))
366355

367356
if (options.updateCDNSettings) {
368357
cdnSettings = options.updateCDNSettings(cdnSettings)

packages/browser/src/core/analytics/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
isSegmentPlugin,
5656
SegmentIOPluginMetadata,
5757
} from '../../plugins/segmentio'
58+
import { HTTPClient, HTTPFetchFn } from '../http-client'
5859

5960
const deprecationWarning =
6061
'This is being deprecated and will be not be available in future releases of Analytics JS'
@@ -178,6 +179,11 @@ export interface InitOptions {
178179
* ```
179180
*/
180181
disable?: boolean | ((cdnSettings: CDNSettings) => boolean | Promise<boolean>)
182+
183+
/**
184+
* Override the fetch function used by the analytics instance.
185+
*/
186+
httpClient?: HTTPFetchFn
181187
}
182188

183189
/* analytics-classic stubs */
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { HTTPClient } from '.'
2+
import { CDNSettings } from '../../browser'
3+
4+
export async function loadCDNSettings(
5+
client: HTTPClient,
6+
{ writeKey, baseUrl }: { writeKey: string; baseUrl: string }
7+
): Promise<CDNSettings> {
8+
return client
9+
.makeRequest({
10+
url: `${baseUrl}/v1/projects/${writeKey}/settings`,
11+
method: 'GET',
12+
})
13+
.then((res) => {
14+
if (!res.ok) {
15+
return res.text().then((errorResponseMessage) => {
16+
throw new Error(errorResponseMessage)
17+
})
18+
}
19+
return res.json()
20+
})
21+
.catch((err) => {
22+
console.error(err.message)
23+
throw err
24+
})
25+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { fetch as defaultFetch } from '../../lib/fetch'
2+
/**
3+
* This interface is meant to be compatible with different fetch implementations (node and browser).
4+
* Using the ambient fetch type is not possible because the AbortSignal type is not compatible with node-fetch.
5+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
6+
*/
7+
export interface HTTPFetchFn {
8+
(url: string, requestInit: HTTPFetchRequest): Promise<HTTPResponse>
9+
}
10+
11+
/**
12+
* This interface is meant to be compatible with the Request interface.
13+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Request
14+
*/
15+
export interface HTTPFetchRequest {
16+
headers?: Record<string, string>
17+
body?: string
18+
method: HTTPClientRequest['method']
19+
}
20+
21+
/**
22+
* This interface is meant to be compatible with the Headers interface.
23+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Headers
24+
*/
25+
export interface HTTPHeaders {
26+
get: (key: string) => string | null
27+
has: (key: string) => boolean
28+
entries: () => IterableIterator<[string, any]>
29+
}
30+
31+
/**
32+
* This interface is meant to very minimally conform to the Response interface.
33+
* @link https://developer.mozilla.org/en-US/docs/Web/API/Response
34+
*/
35+
export interface HTTPResponse {
36+
headers?: Record<string, any> | HTTPHeaders
37+
text: () => Promise<string>
38+
json: () => Promise<any>
39+
ok: boolean
40+
status: number
41+
statusText: string
42+
}
43+
44+
/**
45+
* This interface is meant to be a generic interface for making HTTP requests.
46+
* While it may overlap with fetch's Request interface, it is not coupled to it.
47+
*/
48+
export interface HTTPClientRequest {
49+
/**
50+
* URL to be used for the request
51+
* @example 'https://api.segment.io/v1/t'
52+
*/
53+
url: string
54+
/**
55+
* HTTP method to be used for the request.
56+
**/
57+
method: string
58+
/**
59+
* Headers to be sent with the request
60+
*/
61+
headers?: Record<string, string>
62+
/**
63+
* Data to be sent with the request
64+
*/
65+
body?: string
66+
67+
/**
68+
* Other options defined by the fetch API
69+
*/
70+
[key: string]: unknown
71+
}
72+
73+
/**
74+
* HTTP client interface for making requests
75+
*/
76+
export interface HTTPClient {
77+
makeRequest(_options: HTTPClientRequest): Promise<HTTPResponse>
78+
}
79+
80+
/**
81+
* Default HTTP client implementation using fetch
82+
*/
83+
export class FetchHTTPClient implements HTTPClient {
84+
private _fetch: HTTPFetchFn
85+
constructor(fetchFn?: HTTPFetchFn) {
86+
this._fetch = fetchFn ?? defaultFetch
87+
}
88+
makeRequest(options: HTTPClientRequest): Promise<HTTPResponse> {
89+
return this._fetch(options.url, options)
90+
}
91+
}

0 commit comments

Comments
 (0)