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

refactor!: access-client store decoupling #228

Merged
merged 14 commits into from
Dec 6, 2022
8 changes: 4 additions & 4 deletions packages/access-api/test/helpers/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ export async function context() {

return {
mf,
conn: await connection(
conn: connection({
principal,
// @ts-ignore
mf.dispatchFetch.bind(mf),
new URL('http://localhost:8787')
),
fetch: mf.dispatchFetch.bind(mf),
url: new URL('http://localhost:8787'),
}),
service: Signer.parse(bindings.PRIVATE_KEY),
issuer: principal,
db: new D1QB(db),
Expand Down
4 changes: 4 additions & 0 deletions packages/access-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"exports": {
".": "./src/index.js",
"./agent": "./src/agent.js",
"./drivers/*": "./src/drivers/*.js",
"./stores/*": "./src/stores/*.js",
"./types": "./src/types.js",
"./encoding": "./src/encoding.js"
Expand All @@ -40,6 +41,9 @@
"types": [
"dist/src/types"
],
"drivers/*": [
"dist/src/drivers/*"
],
"stores/*": [
"dist/src/stores/*"
],
Expand Down
145 changes: 145 additions & 0 deletions packages/access-client/src/agent-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Signer } from '@ucanto/principal'
import { Signer as EdSigner } from '@ucanto/principal/ed25519'
import { importDAG } from '@ucanto/core/delegation'
import { CID } from 'multiformats'

/** @typedef {import('./types').AgentDataModel} AgentDataModel */

/** @implements {AgentDataModel} */
export class AgentData {
/** @type {(data: import('./types').AgentDataExport) => Promise<void> | void} */
#save

/**
* @param {import('./types').AgentDataModel} data
* @param {import('./types').AgentDataOptions} [options]
*/
constructor(data, options = {}) {
this.meta = data.meta
this.principal = data.principal
this.spaces = data.spaces
this.delegations = data.delegations
this.currentSpace = data.currentSpace
this.#save = options.store ? options.store.save : () => {}
}

/**
* Create a new AgentData instance from the passed initialization data.
*
* @param {Partial<import('./types').AgentDataModel>} [init]
* @param {import('./types').AgentDataOptions} [options]
*/
static async create(init = {}, options = {}) {
const agentData = new AgentData(
{
meta: { name: 'agent', type: 'device', ...init.meta },
principal: init.principal ?? (await EdSigner.generate()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who knows which signer to use for the agent principal ? With the current code its always ed but browser need rsa

Copy link
Member Author

@alanshaw alanshaw Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, w3up-client/w3ui would choose RSA. I don't think the store implementation should choose. You can put Ed25519 keys in IndexedDB. It's the environment/application that should choose this.

We should create an agent-data.browser.js which just imports agent-data.js and overrides though.

spaces: init.spaces ?? new Map(),
delegations: init.delegations ?? new Map(),
currentSpace: init.currentSpace,
},
options
)
if (options.store) {
await options.store.save(agentData.export())
}
return agentData
}

/**
* Instantiate AgentData from previously exported data.
*
* @param {import('./types').AgentDataExport} raw
* @param {import('./types').AgentDataOptions} [options]
*/
static fromExport(raw, options) {
/** @type {import('./types').AgentDataModel['delegations']} */
const dels = new Map()

for (const [key, value] of raw.delegations) {
dels.set(key, {
delegation: importDAG(
value.delegation.map((d) => ({
cid: CID.parse(d.cid),
bytes: d.bytes,
}))
),
meta: value.meta,
})
}

return new AgentData(
{
meta: raw.meta,
// @ts-expect-error
principal: Signer.from(raw.principal),
currentSpace: raw.currentSpace,
spaces: raw.spaces,
delegations: dels,
},
options
)
}

/**
* Export data in a format safe to pass to `structuredClone()`.
*/
export() {
/** @type {import('./types').AgentDataExport} */
const raw = {
meta: this.meta,
principal: this.principal.toArchive(),
currentSpace: this.currentSpace,
spaces: this.spaces,
delegations: new Map(),
}
for (const [key, value] of this.delegations) {
raw.delegations.set(key, {
meta: value.meta,
delegation: [...value.delegation.export()].map((b) => ({
cid: b.cid.toString(),
bytes: b.bytes,
})),
})
}
return raw
}

/**
* @param {import('@ucanto/interface').DID} did
* @param {import('./types').SpaceMeta} meta
* @param {import('@ucanto/interface').Delegation} [proof]
*/
async addSpace(did, meta, proof) {
this.spaces.set(did, meta)
await (proof ? this.addDelegation(proof) : this.#save(this.export()))
}

/**
* @param {import('@ucanto/interface').DID} did
*/
async setCurrentSpace(did) {
this.currentSpace = did
await this.#save(this.export())
}

/**
* @param {import('@ucanto/interface').Delegation} delegation
* @param {import('./types').DelegationMeta} [meta]
*/
async addDelegation(delegation, meta) {
this.delegations.set(delegation.cid.toString(), {
delegation,
meta: meta ?? {},
})
await this.#save(this.export())
}

/**
* @param {import('@ucanto/interface').UCANLink} cid
*/
async removeDelegation(cid) {
this.delegations.delete(cid.toString())
await this.#save(this.export())
}
}
Loading