Skip to content

Commit 0f96c30

Browse files
authored
Merge pull request #69 from seamapi/seam-api-request-url
Support for SeamHttpRequest url param
2 parents f38c34d + 34efe7d commit 0f96c30

File tree

2 files changed

+155
-32
lines changed

2 files changed

+155
-32
lines changed

src/lib/seam/connect/seam-http-request.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@ export class SeamHttpRequest<
4646

4747
public get url(): URL {
4848
const { client } = this.#parent
49-
const baseUrl = client.defaults.baseURL
50-
if (baseUrl === undefined) {
51-
throw new Error('baseUrl is required')
52-
}
53-
54-
const params = this.#config.params
55-
if (params === undefined) {
56-
return new URL(this.#config.path, baseUrl)
57-
}
49+
const { params } = this.#config
5850

5951
const serializer =
6052
typeof client.defaults.paramsSerializer === 'function'
6153
? client.defaults.paramsSerializer
6254
: serializeUrlSearchParams
6355

64-
return new URL(`${this.#config.path}?${serializer(params)}`, baseUrl)
56+
const origin = getUrlPrefix(client.defaults.baseURL ?? '')
57+
58+
const pathname = this.#config.path.startsWith('/')
59+
? this.#config.path
60+
: `/${this.#config.path}`
61+
62+
const path = params == null ? pathname : `${pathname}?${serializer(params)}`
63+
64+
return new URL(`${origin}${path}`)
6565
}
6666

6767
public get method(): Method {
@@ -128,3 +128,18 @@ export class SeamHttpRequest<
128128
return this.execute().then(onfulfilled, onrejected)
129129
}
130130
}
131+
132+
const getUrlPrefix = (input: string): string => {
133+
if (URL.canParse(input)) {
134+
const url = new URL(input).toString()
135+
if (url.endsWith('/')) return url.slice(0, -1)
136+
return url
137+
}
138+
if (globalThis.location != null) {
139+
const pathname = input.startsWith('/') ? input : `/${input}`
140+
return new URL(`${globalThis.location.origin}${pathname}`).toString()
141+
}
142+
throw new Error(
143+
`Cannot resolve origin from ${input} in a non-browser environment`,
144+
)
145+
}

test/seam/connect/seam-http-request.test.ts

Lines changed: 130 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,163 @@ import { SeamHttp } from '@seamapi/http/connect'
55

66
import { SeamHttpRequest } from 'lib/seam/connect/seam-http-request.js'
77

8-
test('returns a SeamHttpRequest', async (t) => {
8+
test('SeamHttp: returns a SeamHttpRequest', async (t) => {
99
const { seed, endpoint } = await getTestServer(t)
1010
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
1111

12-
const deviceRequest = seam.devices.get({ device_id: seed.august_device_1 })
12+
const request = seam.devices.get({ device_id: seed.august_device_1 })
1313

14-
t.true(deviceRequest instanceof SeamHttpRequest)
15-
t.is(deviceRequest.url.pathname, '/devices/get')
16-
t.deepEqual(deviceRequest.body, {
14+
t.true(request instanceof SeamHttpRequest)
15+
t.truthy(request.url)
16+
t.is(request.responseKey, 'device')
17+
t.deepEqual(request.body, {
1718
device_id: seed.august_device_1,
1819
})
19-
t.is(deviceRequest.responseKey, 'device')
20-
const device = await deviceRequest
20+
21+
const device = await request
2122
t.is(device.workspace_id, seed.seed_workspace_1)
2223
t.is(device.device_id, seed.august_device_1)
2324

2425
// Ensure that the type of the response is correct.
25-
type Expected = ResponseFromSeamHttpRequest<typeof deviceRequest>
26-
26+
type Expected = ResponseFromSeamHttpRequest<typeof request>
2727
const validDeviceType: Expected['device_type'] = 'august_lock'
2828
t.truthy(validDeviceType)
2929

30-
// @ts-expect-error because it's an invalid device type.
30+
// @ts-expect-error invalid device type.
3131
const invalidDeviceType: Expected['device_type'] = 'invalid_device_type'
3232
t.truthy(invalidDeviceType)
3333
})
3434

35-
test("populates SeamHttpRequest's url property", async (t) => {
35+
test('SeamHttpRequest: url is a URL for post requests', async (t) => {
36+
const { seed, endpoint } = await getTestServer(t)
37+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
38+
39+
const { url } = seam.devices.get({ device_id: 'abc123' })
40+
41+
t.true(url instanceof URL)
42+
t.deepEqual(
43+
toPlainUrlObject(url),
44+
toPlainUrlObject(new URL(`${endpoint}/devices/get`)),
45+
)
46+
})
47+
48+
test('SeamHttpRequest: url is a URL for get requests', async (t) => {
3649
const { seed, endpoint } = await getTestServer(t)
3750
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
3851

39-
const deviceRequest = seam.devices.get({ device_id: 'abc123' })
52+
const { url } = seam.connectWebviews.view({
53+
connect_webview_id: 'connect_webview_1',
54+
auth_token: 'auth_token_1',
55+
})
56+
57+
t.true(url instanceof URL)
58+
t.deepEqual(
59+
toPlainUrlObject(url),
60+
toPlainUrlObject(
61+
new URL(
62+
`${endpoint}/connect_webviews/view?auth_token=auth_token_1&connect_webview_id=connect_webview_1`,
63+
),
64+
),
65+
)
66+
})
67+
68+
test('SeamHttpRequest: url is a URL when endpoint is a url without a path', async (t) => {
69+
const { seed } = await getTestServer(t)
70+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
71+
endpoint: 'https://example.com',
72+
})
73+
74+
const { url } = seam.devices.get({ device_id: 'abc123' })
4075

41-
t.is(deviceRequest.url.pathname, '/devices/get')
42-
t.is(deviceRequest.url.search, '')
76+
t.true(url instanceof URL)
77+
t.deepEqual(
78+
toPlainUrlObject(url),
79+
toPlainUrlObject(new URL('https://example.com/devices/get')),
80+
)
81+
})
4382

44-
const connectWebviewsViewRequest = seam.connectWebviews.view({
45-
connect_webview_id: 'abc123',
46-
auth_token: 'invalid',
83+
test('SeamHttpRequest: url is a URL when endpoint is a url with a path', async (t) => {
84+
const { seed } = await getTestServer(t)
85+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
86+
endpoint: 'https://example.com/some/sub/path',
4787
})
4888

49-
t.is(connectWebviewsViewRequest.url.pathname, '/connect_webviews/view')
50-
t.is(connectWebviewsViewRequest.url.searchParams.get('auth_token'), 'invalid')
51-
t.is(
52-
connectWebviewsViewRequest.url.searchParams.get('connect_webview_id'),
53-
'abc123',
89+
const { url } = seam.devices.get({ device_id: 'abc123' })
90+
91+
t.true(url instanceof URL)
92+
t.deepEqual(
93+
toPlainUrlObject(url),
94+
toPlainUrlObject(new URL('https://example.com/some/sub/path/devices/get')),
5495
)
5596
})
5697

98+
test.failing(
99+
'SeamHttpRequest: url is a URL when endpoint is path',
100+
async (t) => {
101+
const { seed } = await getTestServer(t)
102+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
103+
endpoint: '/some/sub/path',
104+
})
105+
106+
const { url } = seam.devices.get({ device_id: 'abc123' })
107+
108+
t.true(url instanceof URL)
109+
t.deepEqual(
110+
toPlainUrlObject(url),
111+
toPlainUrlObject(
112+
new URL('https://example.com/some/sub/path/devices/get'),
113+
),
114+
)
115+
},
116+
)
117+
118+
test.failing(
119+
'SeamHttpRequest: url is a URL when endpoint is empty',
120+
async (t) => {
121+
const { seed } = await getTestServer(t)
122+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
123+
endpoint: '',
124+
})
125+
126+
// TODO: Set globalThis.location.origin = 'https://example.com'
127+
128+
const { url } = seam.devices.get({ device_id: 'abc123' })
129+
130+
t.true(url instanceof URL)
131+
t.deepEqual(
132+
toPlainUrlObject(url),
133+
toPlainUrlObject(new URL('https://example.com/devices/get')),
134+
)
135+
},
136+
)
137+
138+
test('SeamHttpRequest: url throws if unable to resolve origin', async (t) => {
139+
const { seed } = await getTestServer(t)
140+
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
141+
endpoint: '',
142+
})
143+
144+
const request = seam.devices.get({ device_id: 'abc123' })
145+
146+
t.throws(() => request.url, { message: /Cannot resolve origin/ })
147+
})
148+
149+
const toPlainUrlObject = (url: URL): Omit<URL, 'searchParams' | 'toJSON'> => {
150+
return {
151+
pathname: url.pathname,
152+
hash: url.hash,
153+
hostname: url.hostname,
154+
protocol: url.protocol,
155+
username: url.username,
156+
port: url.port,
157+
password: url.password,
158+
host: url.host,
159+
href: url.href,
160+
origin: url.origin,
161+
search: url.search,
162+
}
163+
}
164+
57165
type ResponseFromSeamHttpRequest<T> =
58166
T extends SeamHttpRequest<any, infer TResponse, infer TResponseKey>
59167
? TResponseKey extends keyof TResponse

0 commit comments

Comments
 (0)