Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: only use native fetch type and Headers object #104

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/client/src/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { sha256 } from 'multiformats/hashes/sha2'
* @param {API.ConnectionOptions<T>} options
* @returns {API.ConnectionView<T>}
*/
export const connect = (options) => new Connection(options)
export const connect = options => new Connection(options)

/**
* @template {Record<string, any>} T
Expand Down
23 changes: 17 additions & 6 deletions packages/client/test/client.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ test('encode inovocation', async () => {

const payload = await connection.encoder.encode([add])

assert.deepEqual(payload.headers, {
'content-type': 'application/car',
})
assert.deepEqual(
payload.headers,
new Headers({
'content-type': 'application/car',
})
)
assert.ok(payload.body instanceof Uint8Array)

const request = await CAR.decode(payload)
Expand Down Expand Up @@ -142,8 +145,16 @@ const connection = Client.connect({
id: w3,
channel: HTTP.open({
url: new URL('about:blank'),
// @ts-expect-error - we are mocking fetch it does not implement of the type properties
fetch: async (url, input) => {
const invocations = await CAR.decode(input)
if (!input) {
return new Error('missing input')
}
const invocations = await CAR.decode({
// @ts-ignore
body: input.body,
headers: new Headers(input.headers),
})
const promises = invocations.map(invocation => {
const [capabality] = invocation.capabilities
switch (capabality.can) {
Expand All @@ -162,11 +173,11 @@ const connection = Client.connect({

const results = await Promise.all(promises)

const { headers, body } = await CBOR.encode(results)
const { headers, body } = CBOR.encode(results)

return {
ok: true,
headers: new Map(Object.entries(headers)),
headers,
arrayBuffer: () => body,
}
},
Expand Down
4 changes: 2 additions & 2 deletions packages/interface/src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ export interface ResponseDecoder {

export interface HTTPRequest<T = unknown> extends Phantom<T> {
method?: string
headers: Readonly<Record<string, string>>
headers: Headers
body: Uint8Array
}

export interface HTTPResponse<T = unknown> extends Phantom<T> {
headers: Readonly<Record<string, string>>
headers: Headers
body: Uint8Array
}

Expand Down
4 changes: 2 additions & 2 deletions packages/transport/src/car.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const encode = async (invocations, options) => {
const body = CAR.encode({ roots, blocks })

return {
headers: HEADERS,
headers: new Headers(HEADERS),
body,
}
}
Expand All @@ -43,7 +43,7 @@ export const encode = async (invocations, options) => {
* @returns {Promise<API.InferInvocations<Invocations>>}
*/
export const decode = async ({ headers, body }) => {
const contentType = headers['content-type'] || headers['Content-Type']
const contentType = headers.get('content-type')
if (contentType !== 'application/car') {
throw TypeError(
`Only 'content-type: application/car' is supported, instead got '${contentType}'`
Expand Down
8 changes: 4 additions & 4 deletions packages/transport/src/cbor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export const codec = CBOR
* @param {I} result
* @returns {API.HTTPResponse<I>}
*/
export const encode = (result) => {
export const encode = result => {
return {
headers: HEADERS,
headers: new Headers(HEADERS),
body: CBOR.encode(result),
}
}
Expand All @@ -29,10 +29,10 @@ export const encode = (result) => {
* @returns {Promise<I>}
*/
export const decode = async ({ headers, body }) => {
const contentType = headers['content-type'] || headers['Content-Type']
const contentType = headers.get('content-type')
if (contentType !== 'application/cbor') {
throw TypeError(
`Only 'content-type: application/cbor' is supported, intsead got '${contentType}'`
`Only 'content-type: application/cbor' is supported, instead got '${contentType}'`
)
}

Expand Down
17 changes: 3 additions & 14 deletions packages/transport/src/http.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import * as API from '@ucanto/interface'

/**
* @typedef {{
* ok: boolean
* arrayBuffer():API.Await<ArrayBuffer>
* headers: {
* entries():Iterable<[string, string]>
* }
* status?: number
* statusText?: string
* url?: string
* }} FetchResponse
* @typedef {(url:string, init:API.HTTPRequest<API.Tuple<API.ServiceInvocation>>) => API.Await<FetchResponse>} Fetcher
* @typedef {typeof fetch} Fetcher
*/
/**
* @template T
* @param {object} options
* @param {URL} options.url
* @param {(url:string, init:API.HTTPRequest<API.Tuple<API.ServiceInvocation>>) => API.Await<FetchResponse>} [options.fetch]
* @param {Fetcher} [options.fetch]
* @param {string} [options.method]
* @returns {API.Channel<T>}
*/
Expand Down Expand Up @@ -62,7 +51,7 @@ class Channel {
: HTTPError.throw('HTTP Request failed', response)

return {
headers: Object.fromEntries(response.headers.entries()),
headers: response.headers,
body: new Uint8Array(buffer),
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/transport/src/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const encode = async batch => {
}

return {
headers,
headers: new Headers(headers),
body: UTF8.encode(JSON.stringify(body)),
}
}
Expand All @@ -57,7 +57,7 @@ const iterate = function* (delegation) {
* @returns {Promise<API.InferInvocations<I>>}
*/
export const decode = async ({ headers, body }) => {
const contentType = headers['content-type'] || headers['Content-Type']
const contentType = headers.get('content-type')
if (contentType !== 'application/json') {
throw TypeError(
`Only 'content-type: application/json' is supported, instead got '${contentType}'`
Expand All @@ -66,7 +66,7 @@ export const decode = async ({ headers, body }) => {
/** @type {API.Block[]} */
const invocations = []
const blocks = new Map()
for (const [name, value] of Object.entries(headers)) {
for (const [name, value] of headers.entries()) {
if (name.startsWith(HEADER_PREFIX)) {
const key = name.slice(HEADER_PREFIX.length)
const data = UCAN.parse(/** @type {UCAN.JWT<any>} */ (value))
Expand Down
17 changes: 10 additions & 7 deletions packages/transport/test/car.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ test('encode / decode', async () => {
},
])

assert.deepEqual(request.headers, {
'content-type': 'application/car',
})
assert.deepEqual(
request.headers,
new Headers({
'content-type': 'application/car',
})
)
const reader = await CarReader.fromBytes(request.body)

assert.deepEqual(await reader.getRoots(), [cid])
Expand Down Expand Up @@ -73,9 +76,9 @@ test('decode requires application/car contet type', async () => {
try {
await CAR.decode({
body,
headers: {
headers: new Headers({
'content-type': 'application/octet-stream',
},
}),
})
assert.fail('expected to fail')
} catch (error) {
Expand All @@ -102,9 +105,9 @@ test('accepts Content-Type as well', async () => {

const [invocation] = await CAR.decode({
...request,
headers: {
headers: new Headers({
'Content-Type': 'application/car',
},
}),
})

const delegation = await delegate({
Expand Down
8 changes: 4 additions & 4 deletions packages/transport/test/cbor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ test('encode / decode', async () => {
const response = CBOR.encode([{ ok: true, value: 1 }])

assert.deepEqual(response, {
headers: {
headers: new Headers({
'content-type': 'application/cbor',
},
}),
body: encode([{ ok: true, value: 1 }]),
})

Expand All @@ -19,7 +19,7 @@ test('encode / decode', async () => {
test('throws on wrong content-type', async () => {
try {
await CBOR.decode({
headers: { 'content-type': 'application/octet-stream' },
headers: new Headers({ 'content-type': 'application/octet-stream' }),
body: encode([{ ok: true, value: 1 }]),
})
assert.fail('should have failed')
Expand All @@ -31,7 +31,7 @@ test('throws on wrong content-type', async () => {
test('content-type case', async () => {
assert.deepEqual(
await CBOR.decode({
headers: { 'Content-Type': 'application/cbor' },
headers: new Headers({ 'Content-Type': 'application/cbor' }),
body: encode([{ ok: true, value: 1 }]),
}),
[{ ok: true, value: 1 }]
Expand Down
22 changes: 16 additions & 6 deletions packages/transport/test/https.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CAR, JWT, CBOR } from '../src/lib.js'
test('encode / decode', async () => {
const channel = HTTP.open({
url: new URL('about:blank'),
// @ts-expect-error - we are mocking fetch it does not implement of the type properties
fetch: async (url, init) => {
assert.equal(url.toString(), 'about:blank')
assert.equal(init?.method, 'POST')
Expand All @@ -18,14 +19,22 @@ test('encode / decode', async () => {
})

const response = await channel.request({
headers: { 'content-type': 'text/plain' },
headers: new Headers({ 'content-type': 'text/plain' }),
body: UTF8.encode('ping'),
})

assert.deepEqual(response, {
headers: { 'content-type': 'text/plain' },
body: UTF8.encode('pong'),
})
assert.deepEqual(
{
headers: Object.entries(response.headers.entries()),
body: response.body,
},
{
headers: Object.entries(
new Headers({ 'content-type': 'text/plain' }).entries()
),
body: UTF8.encode('pong'),
}
)
})

if (!globalThis.fetch) {
Expand All @@ -40,6 +49,7 @@ if (!globalThis.fetch) {
test('failed request', async () => {
const channel = HTTP.open({
url: new URL('https://ucan.xyz/'),
// @ts-expect-error - we are mocking fetch it does not implement of the type properties
fetch: async (url, init) => {
return {
ok: false,
Expand All @@ -54,7 +64,7 @@ test('failed request', async () => {

try {
await channel.request({
headers: { 'content-type': 'text/plain' },
headers: new Headers({ 'content-type': 'text/plain' }),
body: UTF8.encode('ping'),
})
assert.fail('expected to throw')
Expand Down
31 changes: 16 additions & 15 deletions packages/transport/test/jwt.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ test('encode / decode', async () => {

const expect = {
body: UTF8.encode(JSON.stringify([cid])),
headers: {
headers: new Headers({
'content-type': 'application/json',
[`x-auth-${cid}`]: jwt,
},
}),
}

assert.deepEqual(request, expect)
Expand Down Expand Up @@ -69,9 +69,9 @@ test('decode requires application/json contet type', async () => {
try {
await JWT.decode({
body: UTF8.encode(JSON.stringify([cid])),
headers: {
headers: new Headers({
[`x-auth-${cid}`]: jwt,
},
}),
})
assert.fail('expected to fail')
} catch (error) {
Expand Down Expand Up @@ -108,7 +108,7 @@ test('delegated proofs', async () => {
},
])

assert.equal(Object.keys(outgoing.headers).length, 3)
assert.equal([...outgoing.headers.entries()].length, 3)

const incoming = await JWT.decode(outgoing)

Expand Down Expand Up @@ -159,7 +159,7 @@ test('omit proof', async () => {
},
])

assert.equal(Object.keys(outgoing.headers).length, 2)
assert.equal([...outgoing.headers.entries()].length, 2)

const incoming = await JWT.decode(outgoing)

Expand Down Expand Up @@ -209,17 +209,18 @@ test('thorws on invalid heard', async () => {
expiration,
},
])

const { [`x-auth-${proof.cid}`]: jwt, ...headers } = request.headers
const newHeaders = new Headers(request.headers)
newHeaders.delete(`x-auth-${proof.cid}`)
newHeaders.set(
`x-auth-bafyreigw75rhf7gf7eubwmrhovcrdu4mfy6pfbi4wgbzlfieq2wlfsza5i`,
// @ts-ignore
request.headers.get(`x-auth-${proof.cid}`)
)

try {
await JWT.decode({
...request,
headers: {
...headers,
[`x-auth-bafyreigw75rhf7gf7eubwmrhovcrdu4mfy6pfbi4wgbzlfieq2wlfsza5i`]:
request.headers[`x-auth-${proof.cid}`],
},
headers: newHeaders,
})
assert.fail('expected to fail')
} catch (error) {
Expand Down Expand Up @@ -269,12 +270,12 @@ test('leaving out root throws', async () => {
expiration,
})

const { [`x-auth-${cid}`]: jwt, ...headers } = request.headers
request.headers.delete(`x-auth-${cid}`)

try {
await JWT.decode({
...request,
headers,
headers: request.headers,
})
assert.fail('expected to fail')
} catch (error) {
Expand Down