Skip to content

Commit

Permalink
set default gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
fforbeck committed Dec 16, 2024
1 parent 6849fa4 commit 81eb44c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 47 deletions.
4 changes: 3 additions & 1 deletion packages/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ export async function remove(rootCid, opts) {
*/
export async function createSpace(name) {
const client = await getClient()
const space = await client.createSpace(name)
const space = await client.createSpace(name, {
skipGatewayAuthorization: true,
})
await client.setCurrentSpace(space.did())
console.log(space.did())
}
Expand Down
29 changes: 15 additions & 14 deletions packages/cli/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,25 @@ import * as Result from '@storacha/client/result'
export const create = async (name, options) => {
const client = await getClient()
const spaces = client.spaces()

let space
if (options.skipGatewayAuthorization === true) {
space = await client.createSpace(await chooseName(name ?? '', spaces), {
skipGatewayAuthorization: true,
})
} else {
const gateways = options.authorizeGatewayServices ?? []
const connections = gateways.map(({ id, serviceEndpoint }) =>
UcantoClient.connect({
const connections = gateways.map(({ id, serviceEndpoint }) => {
/** @type {UcantoClient.ConnectionView<import('@storacha/client/types').ContentServeService>} */
const connection = UcantoClient.connect({
id: {
did: () => id,
},
codec: CAR.outbound,
channel: HTTP.open({ url: new URL(serviceEndpoint) }),
})
)
})
return connection
})
space = await client.createSpace(await chooseName(name ?? '', spaces), {
authorizeGatewayServices: connections,
})
Expand Down Expand Up @@ -209,7 +211,7 @@ export const provision = async (name = '', options = {}) => {
const { ok: bytes, error: fetchError } = await fetch(options.coupon)
.then((response) => response.arrayBuffer())
.then((buffer) => Result.ok(new Uint8Array(buffer)))
.catch((error) => Result.error(/** @type {Error} */ (error)))
.catch((error) => Result.error(/** @type {Error} */(error)))

if (fetchError) {
console.error(`Failed to fetch coupon from ${options.coupon}`)
Expand Down Expand Up @@ -245,8 +247,7 @@ export const provision = async (name = '', options = {}) => {

if (result.error) {
console.error(
`⚠️ Failed to set up billing account,\n ${
Object(result.error).message ?? ''
`⚠️ Failed to set up billing account,\n ${Object(result.error).message ?? ''
}`
)
process.exit(1)
Expand Down Expand Up @@ -285,7 +286,7 @@ const chooseSpace = (client, { name }) => {
* @param {W3Space.Model} space
* @param {CreateOptions} options
*/
export const setupEmailRecovery = async (space, options = {}) => {}
export const setupEmailRecovery = async (space, options = {}) => { }

/**
* @param {string} email
Expand Down Expand Up @@ -347,8 +348,8 @@ const chooseName = async (name, spaces) => {
name === ''
? 'What would you like to call this space?'
: space
? `Name "${space.name}" is already taken, please choose a different one`
: null
? `Name "${space.name}" is already taken, please choose a different one`
: null

if (message == null) {
return name
Expand Down Expand Up @@ -415,9 +416,9 @@ export const setupAccount = async (client) => {

return email
? await Account.loginWithClient(
/** @type {DIDMailto.EmailAddress} */ (email),
client
)
/** @type {DIDMailto.EmailAddress} */(email),
client
)
: null
}

Expand Down
1 change: 1 addition & 0 deletions packages/w3up-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"mock:bucket-0-200": "PORT=8989 STATUS=200 node test/helpers/bucket-server.js",
"mock:bucket-1-200": "PORT=8990 STATUS=200 node test/helpers/bucket-server.js",
"mock:receipts-server": "PORT=9201 node test/helpers/receipts-server.js",
"mock:gateway-server": "PORT=5001 node test/helpers/gateway-server.js",
"coverage": "c8 report -r html && open coverage/index.html",
"rc": "npm version prerelease --preid rc",
"docs": "npm run build && typedoc --out docs-generated"
Expand Down
37 changes: 27 additions & 10 deletions packages/w3up-client/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import { FilecoinClient } from './capability/filecoin.js'
import { CouponAPI } from './coupon.js'
export * as Access from './capability/access.js'
import * as Result from './result.js'
import * as UcantoClient from '@ucanto/client'
import { HTTP } from '@ucanto/transport'
import * as CAR from '@ucanto/transport/car'

export {
AccessClient,
Expand Down Expand Up @@ -264,7 +267,7 @@ export class Client extends Base {
* @param {SpaceCreateOptions} options - Options for the space creation.
* @returns {Promise<import("./space.js").OwnedSpace>} The created space owned by the agent.
*/
async createSpace(name, options = {}) {
async createSpace(name, options) {
// Save the space to authorize the client to use the space
const space = await this._agent.createSpace(name)

Expand Down Expand Up @@ -301,16 +304,30 @@ export class Client extends Base {

// Authorize the listed Gateway Services to serve content from the created space
if (options.skipGatewayAuthorization !== true) {
if (
!options.authorizeGatewayServices ||
options.authorizeGatewayServices.length === 0
) {
throw new Error(
'failed to authorize Gateway Services: missing <authorizeGatewayServices> option'
)
let authorizeGatewayServices = options.authorizeGatewayServices
if (!authorizeGatewayServices || authorizeGatewayServices.length === 0) {
// If no Gateway Services are provided, authorize the Storacha Gateway Service
authorizeGatewayServices = [
UcantoClient.connect({
id: {
did: () =>
/** @type {`did:${string}:${string}`} */(
/* c8 ignore next - default prod gateway id is not used in tests */
process.env.DEFAULT_GATEWAY_ID ?? 'did:web:w3s.link'
),
},
codec: CAR.outbound,
channel: HTTP.open({
url: new URL(
/* c8 ignore next - default prod gateway url is not used in tests */
process.env.DEFAULT_GATEWAY_URL ?? 'https://freeway.dag.haus'
),
}),
}),
]
}

for (const serviceConnection of options.authorizeGatewayServices) {
for (const serviceConnection of authorizeGatewayServices) {
await authorizeContentServe(this, space, serviceConnection)
}
}
Expand Down Expand Up @@ -594,7 +611,7 @@ export const authorizeContentServe = async (
/* c8 ignore next 8 - can't mock this error */
if (verificationResult.out.error) {
throw new Error(
`failed to publish delegation for audience ${options.audience}: ${verificationResult.out.error.message}`,
`failed to publish delegation for audience ${audience.did()}: ${verificationResult.out.error.message}`,
{
cause: verificationResult.out.error,
}
Expand Down
30 changes: 12 additions & 18 deletions packages/w3up-client/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ export const testClient = {
assert.fail(error, 'should not throw when creating the space')
}
},
'should throw when the content serve authorization fails due to missing service configuration':
'should authorize the Storacha Gateway Service when no Gateway Services are provided':
async (assert, { mail, grantAccess, connection }) => {
// Step 1: Create a client for Alice and login
const aliceClient = new Client(
Expand All @@ -668,23 +668,17 @@ export const testClient = {
await grantAccess(message)
const aliceAccount = await aliceLogin

try {
const spaceA = await aliceClient.createSpace(
'authorize-gateway-space',
{
account: aliceAccount,
authorizeGatewayServices: [], // No services to authorize
}
)
assert.fail(spaceA, 'should not create the space')
} catch (error) {
assert.match(
// @ts-expect-error
error.message,
/missing <authorizeGatewayServices> option/,
'should throw when creating the space'
)
}
process.env.DEFAULT_GATEWAY_ID = gateway.did()
process.env.DEFAULT_GATEWAY_URL = 'http://localhost:5001'

const spaceA = await aliceClient.createSpace(
'authorize-gateway-space',
{
account: aliceAccount,
authorizeGatewayServices: [], // If no Gateway Services are provided, authorize the Storacha Gateway Service
}
)
assert.ok(spaceA, 'should create the space')
},
'should throw when content serve service can not process the invocation':
async (assert, { mail, grantAccess, connection }) => {
Expand Down
61 changes: 61 additions & 0 deletions packages/w3up-client/test/helpers/gateway-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createServer } from 'node:http'
import {
createUcantoServer,
getContentServeMockService,
} from '../mocks/service.js'
import { gateway } from '../../../upload-api/test/helpers/utils.js'

const port = 5001

const server = createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', '*')
res.setHeader('Access-Control-Allow-Headers', '*')
if (req.method === 'OPTIONS') return res.end()

if (req.method === 'POST') {
const service = getContentServeMockService()
const server = createUcantoServer(gateway, service)

const bodyBuffer = Buffer.concat(await collect(req))

const reqHeaders = /** @type {Record<string, string>} */ (
Object.fromEntries(Object.entries(req.headers))
)

const { headers, body, status } = await server.request({
body: new Uint8Array(
bodyBuffer.buffer,
bodyBuffer.byteOffset,
bodyBuffer.byteLength
),
headers: reqHeaders,
})

for (const [key, value] of Object.entries(headers)) {
res.setHeader(key, value)
}
res.writeHead(status ?? 200)
res.end(body)
}
res.end()
})

/** @param {import('node:stream').Readable} stream */
const collect = (stream) => {
return /** @type {Promise<Buffer[]>} */ (
new Promise((resolve, reject) => {
const chunks = /** @type {Buffer[]} */ ([])
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)))
stream.on('error', (err) => reject(err))
stream.on('end', () => resolve(chunks))
})
)
}

// eslint-disable-next-line no-console
server.listen(port, () =>
console.log(`[Mock] Gateway Server Listening on :${port}`)
)

process.on('SIGTERM', () => process.exit(0))
18 changes: 14 additions & 4 deletions packages/w3up-client/test/mocks/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,29 @@ export function getContentServeMockService(result = { ok: {} }) {
}

/**
* Generic function to create connection to any type of mock service with any type of signer id.
* Creates a new Ucanto server with the given options.
*
* @param {any} id
* @param {any} service
* @param {string | undefined} [url]
*/
export function getConnection(id, service, url = undefined) {
const server = Server.create({
export function createUcantoServer(id, service) {
return Server.create({
id: id,
service,
codec: CAR.inbound,
validateAuthorization: () => ({ ok: {} }),
})
}

/**
* Generic function to create connection to any type of mock service with any type of signer id.
*
* @param {any} id
* @param {any} service
* @param {string | undefined} [url]
*/
export function getConnection(id, service, url = undefined) {
const server = createUcantoServer(id, service)
const connection = Client.connect({
id: id,
codec: CAR.outbound,
Expand Down

0 comments on commit 81eb44c

Please sign in to comment.