Skip to content

Commit

Permalink
fix: settings export/import
Browse files Browse the repository at this point in the history
  • Loading branch information
hugomrdias committed Oct 27, 2022
1 parent f21b726 commit 27ca9dc
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 49 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"test:dev": "vitest -w"
},
"dependencies": {
"@ipld/car": "^4.1.5",
"@ucanto/client": "^1.0.1",
"@ucanto/core": "^1.0.1",
"@ucanto/interface": "^1.0.0",
Expand Down
110 changes: 110 additions & 0 deletions src/encoding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable unicorn/prefer-spread */
import { CarReader } from '@ipld/car/reader'
import { CarWriter } from '@ipld/car/writer'
import { Delegation } from '@ucanto/core/delegation'
// eslint-disable-next-line no-unused-vars
import * as Types from '@ucanto/interface'
import * as u8 from 'uint8arrays'

/**
* @param {AsyncIterable<Uint8Array>} iterable
*/
function collector(iterable) {
const chunks = []
const cfn = (async () => {
for await (const chunk of iterable) {
chunks.push(chunk)
}
return u8.concat(chunks)
})()
return cfn
}

/**
* @param {Types.Delegation[]} delegations
* @param {import('uint8arrays/to-string').SupportedEncodings} encoding
*/
export async function encodeDelegations(delegations, encoding = 'base64url') {
if (delegations.length === 0) {
return ''
}

const roots = delegations.map((d) => d.root.cid)

// @ts-ignore
const { writer, out } = CarWriter.create(roots)
const collection = collector(out)

for (const delegation of delegations) {
for (const block of delegation.export()) {
// @ts-ignore
await writer.put(block)
}
}
await writer.close()

const bytes = await collection

return u8.toString(bytes, encoding)
}

/**
* Encode one {@link Types.Delegation Delegation} into a string
*
* @param {Types.Delegation<Types.Capabilities>} delegation
* @param {import('uint8arrays/to-string').SupportedEncodings} [encoding]
*/
export function delegationToString(delegation, encoding) {
return encodeDelegations([delegation], encoding)
}

/**
* Decode string into {@link Types.Delegation Delegation}
*
* @template {Types.Capabilities} [T=Types.Capabilities]
* @param {import('./types').EncodedDelegation<T>} raw
* @param {import('uint8arrays/to-string').SupportedEncodings} [encoding]
*/
export async function decodeDelegations(raw, encoding = 'base64url') {
if (!raw) {
return []
}
const bytes = u8.fromString(raw, encoding)
const reader = await CarReader.fromBytes(bytes)
const roots = await reader.getRoots()

/** @type {Types.Delegation<T>[]} */
const delegations = []

for (const root of roots) {
const rootBlock = await reader.get(root)

if (rootBlock) {
const blocks = new Map()
for (const block of reader._blocks) {
if (block.cid.toString() !== root.toString())
blocks.set(block.cid.toString(), block)
}

// @ts-ignore
delegations.push(new Delegation(rootBlock, blocks))
} else {
throw new Error('Failed to find root from raw delegation.')
}
}

return delegations
}

/**
* Decode string into a {@link Types.Delegation Delegation}
*
* @template {Types.Capabilities} [T=Types.Capabilities]
* @param {import('./types').EncodedDelegation<T>} raw
* @param {import('uint8arrays/to-string').SupportedEncodings} [encoding]
*/
export async function stringToDelegation(raw, encoding) {
const delegations = await decodeDelegations(raw, encoding)

return /** @type {Types.Delegation<T>} */ (delegations[0])
}
74 changes: 35 additions & 39 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
generateDelegation,
importDelegation,
} from './delegation.js'
import { delegationToString, stringToDelegation } from './encoding.js'
import * as Settings from './settings.js'
import { Access, Store } from './store/index.js'
import { checkUrl, sleep } from './utils.js'
Expand Down Expand Up @@ -87,15 +88,16 @@ class Client {
* @returns {Promise<API.SigningPrincipal>}
*/
async agent() {
let secret = this.settings.get('agent_secret') || null
const settings = await this.settings
let secret = settings.get('agent_secret') || null

let id = Settings.toPrincipal(secret)
if (!id) {
id = await SigningPrincipal.generate()
}

if (!this.settings.has('agent_secret')) {
this.settings.set('agent_secret', SigningPrincipal.format(id))
if (!settings.has('agent_secret')) {
settings.set('agent_secret', SigningPrincipal.format(id))
}

return id
Expand All @@ -106,20 +108,21 @@ class Client {
* @returns {Promise<API.SigningPrincipal>}
*/
async account() {
let secret = this.settings.get('account_secret') || null
const settings = await this.settings
let secret = settings.get('account_secret') || null

// For now, move old secret value to new account_secret.
if (!secret && this.settings.has('secret')) {
secret = this.settings.get('secret')
if (!secret && settings.has('secret')) {
secret = settings.get('secret')
// this.settings.delete('secret')
}
let id = Settings.toPrincipal(secret)
if (!id) {
id = await SigningPrincipal.generate()
}

if (!this.settings.has('account_secret')) {
this.settings.set('account_secret', SigningPrincipal.format(id))
if (!settings.has('account_secret')) {
settings.set('account_secret', SigningPrincipal.format(id))
}

return id
Expand All @@ -129,39 +132,30 @@ class Client {
* @returns {Promise<API.Delegation|null>}
*/
async currentDelegation() {
let did = this.settings.has('delegation')
? this.settings.get('delegation')
: null
const settings = await this.settings
let account = settings.has('account') ? settings.get('account') : null

let delegations = this.settings.has('delegations')
? this.settings.get('delegations')
let delegations = settings.has('delegations')
? settings.get('delegations')
: {}

//Generate first delegation from account to agent.
if (!did) {
const issuer = await this.account()
const to = (await this.agent()).did()
const del = await generateDelegation({ to, issuer }, true)

did = (await this.account()).did()

delegations[did] = { ucan: del, alias: 'self' }
this.settings.set('delegations', delegations)
this.settings.set('delegation', issuer.did())
}

delegations = this.settings.has('delegations')
? this.settings.get('delegations')
: {}
if (!account) {
const account = await this.account()
const agent = (await this.agent()).did()
const del = await generateDelegation({ to: agent, issuer: account }, true)

delegations[account.did()] = {
ucan: await delegationToString(del),
alias: 'self',
}
settings.set('delegations', delegations)
settings.set('account', account.did())

try {
const ucan = delegations[did]?.ucan
const del = Delegation.import([ucan?.root])
return del
} catch (err) {
console.log('err', err)
return null
}

return stringToDelegation(delegations[account].ucan)
}

/**
Expand Down Expand Up @@ -194,9 +188,10 @@ class Client {
* @param {string|undefined} email - The email address to register with.
*/
async register(email) {
const savedEmail = this.settings.get('email')
const settings = await this.settings
const savedEmail = settings.get('email')
if (!savedEmail) {
this.settings.set('email', email)
settings.set('email', email)
} else if (email !== savedEmail) {
throw new Error(
'Trying to register a second email, this is not supported yet.'
Expand Down Expand Up @@ -313,6 +308,7 @@ class Client {
* @returns {Promise<API.Delegation>}
*/
async importDelegation(bytes, alias = '') {
const settings = await this.settings
const imported = await importDelegation(bytes)
const did = imported.issuer.did()

Expand All @@ -324,12 +320,12 @@ class Client {
)
}

let delegations = this.settings.has('delegations')
? this.settings.get('delegations')
let delegations = settings.has('delegations')
? settings.get('delegations')
: {}

delegations[did] = { ucan: imported, alias }
this.settings.set('delegations', delegations)
settings.set('delegations', delegations)

return imported
}
Expand Down
20 changes: 10 additions & 10 deletions src/settings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Delegation, UCAN } from '@ucanto/core'
import { SigningPrincipal } from '@ucanto/principal'

import { delegationToString, stringToDelegation } from './encoding.js'

/**
* @typedef SettingsObject
* @property {string} [secret]
Expand Down Expand Up @@ -35,9 +37,9 @@ export function toPrincipal(secret) {

/**
* @param {Map<string, any>|SettingsObject} objectToParse
* @returns {Map<string, any>}
* @returns {Promise<Map<string, any>>}
*/
export function objectToMap(objectToParse) {
export async function objectToMap(objectToParse) {
// TODO: CHANGE LATER, store check is only for CONF
if (objectToParse instanceof Map) {
/** @type Map<string, any> */
Expand Down Expand Up @@ -79,7 +81,7 @@ export function objectToMap(objectToParse) {
for (const [did, del] of Object.entries(objectToParse.delegations)) {
// @ts-ignore
delegations[did] = {
ucan: UCAN.parse(del?.ucan),
ucan: await stringToDelegation(del?.ucan),
alias: del.alias,
}
}
Expand All @@ -100,9 +102,9 @@ export function objectToMap(objectToParse) {
* Takes a JSON string and builds a settings object from it.
*
* @param {Map<string,any>|string|SettingsObject} settings - The settings string (typically from cli export-settings)
* @returns {Map<string,any>} The settings object.
* @returns {Promise<Map<string,any>>} The settings object.
*/
export function importSettings(settings) {
export async function importSettings(settings) {
if (typeof settings == 'string') {
try {
return objectToMap(JSON.parse(settings))
Expand All @@ -117,9 +119,9 @@ export function importSettings(settings) {
* Takes a settings map and builds a POJO out of it.
*
* @param {Map<string, any>} settings - The settings object.
* @returns {SettingsObject} The settings object.
* @returns {Promise<SettingsObject>} The settings object.
*/
export function exportSettings(settings) {
export async function exportSettings(settings) {
/** @type SettingsObject */
const output = {}

Expand Down Expand Up @@ -156,11 +158,9 @@ export function exportSettings(settings) {
output.delegations = {}

for (const [did, del] of Object.entries(settings.get('delegations'))) {
const imported = Delegation.import([del?.ucan?.root])

output.delegations[did] = {
// @ts-ignore
ucan: UCAN.format(imported),
ucan: del.ucan,
alias: del.alias,
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { Capabilities, Phantom } from '@ucanto/interface'

export type EncodedDelegation<C extends Capabilities = Capabilities> = string &
Phantom<C>

0 comments on commit 27ca9dc

Please sign in to comment.