diff --git a/packages/ipfs-message-port-client/package.json b/packages/ipfs-message-port-client/package.json index a1db779b95..609a74bbaa 100644 --- a/packages/ipfs-message-port-client/package.json +++ b/packages/ipfs-message-port-client/package.json @@ -42,14 +42,14 @@ "dependencies": { "browser-readablestream-to-it": "^1.0.1", "ipfs-core-types": "^0.3.0", - "ipfs-message-port-protocol": "^0.6.0" + "ipfs-message-port-protocol": "file:../ipfs-message-port-protocol" }, "devDependencies": { "aegir": "^30.3.0", "interface-ipfs-core": "^0.144.1", "ipfs": "^0.54.2", "ipfs-core": "^0.5.2", - "ipfs-message-port-server": "^0.6.1", + "ipfs-message-port-server": "file:../ipfs-message-port-server", "rimraf": "^3.0.2" }, "engines": { diff --git a/packages/ipfs-message-port-client/src/index.js b/packages/ipfs-message-port-client/src/index.js index 205298b822..4c9c2b6042 100644 --- a/packages/ipfs-message-port-client/src/index.js +++ b/packages/ipfs-message-port-client/src/index.js @@ -6,6 +6,7 @@ const BlockClient = require('./block') const DAGClient = require('./dag') const CoreClient = require('./core') const FilesClient = require('./files') +const PinClient = require('./pin') /** * @typedef {Object} ClientOptions @@ -19,9 +20,10 @@ class IPFSClient extends CoreClient { constructor (transport) { super(transport) this.transport = transport + this.block = new BlockClient(this.transport) this.dag = new DAGClient(this.transport) this.files = new FilesClient(this.transport) - this.block = new BlockClient(this.transport) + this.pin = new PinClient(this.transport) } /** diff --git a/packages/ipfs-message-port-client/src/pin.js b/packages/ipfs-message-port-client/src/pin.js new file mode 100644 index 0000000000..dc4bda5b7f --- /dev/null +++ b/packages/ipfs-message-port-client/src/pin.js @@ -0,0 +1,127 @@ +'use strict' + +/* eslint-env browser */ +const Client = require('./client') +const { decodeCID, encodeCID, CID } = require('ipfs-message-port-protocol/src/cid') +const { decodeIterable, encodeIterable } = require('ipfs-message-port-protocol/src/core') + +/** + * @typedef {import('ipfs-message-port-protocol/src/pin').EncodedPin} EncodedPin + * @typedef {import('ipfs-message-port-protocol/src/pin').LsEntry} LsEntry + * @typedef {import('ipfs-message-port-protocol/src/pin').LsOptions} LsOptions + * @typedef {import('ipfs-message-port-protocol/src/pin').Pin} Pin + * @typedef {import('ipfs-message-port-protocol/src/pin').Source} Source + * @typedef {import('ipfs-message-port-server/src/pin').EncodedCID} EncodedCID + * @typedef {import('ipfs-message-port-server/src/pin').EncodedLsEntry} EncodedLsEntry + * @typedef {import('ipfs-message-port-server/src/pin').PinService} PinService + * @typedef {import('./client').MessageTransport} MessageTransport + */ + +/** + * @class + * @extends {Client} + */ +class PinClient extends Client { + /** + * @param {MessageTransport} transport + */ + constructor (transport) { + super('pin', ['add', 'ls', 'rmAll'], transport) + } + + /** + * @param {string|CID} pathOrCID + * @param {Object} [options] + * @property {boolean} [recursive=true] + * @property {number} [timeout] + * @property {AbortSignal} [signal] + * + * @returns {Promise} + */ + async add (pathOrCID, options = {}) { + const { recursive, timeout, signal } = options + const { cid } = await this.remote.add({ + path: encodePathOrCID(pathOrCID), + recursive, + timeout, + signal + }) + return decodeCID(cid) + } + + /** + * @param {LsOptions} [options] + * @returns {AsyncIterable} + */ + async * ls (options = {}) { + let paths = options.paths + let encodedPaths + + if (paths === undefined) { + encodedPaths = paths + } else if (Array.isArray(paths)) { + encodedPaths = paths.map(path => encodePathOrCID(path)) + } else { + encodedPaths = encodePathOrCID(paths) + } + + const result = await this.remote.ls({ ...options, paths: encodedPaths }) + + yield * decodeIterable(result.data, decodeLsEntry) + } + + /** + * @typedef {Object} RmAllOptions + * @property {AbortSignal} [signal] + * @property {number} [timeout] + * + * @param {Source} source + * @param {RmAllOptions} [options] + * @returns {AsyncIterable} + */ + async * rmAll (source, options = {}) { + const transfer = [] + const encodedSource = source[Symbol.asyncIterator] + ? encodeIterable(/** @type {AsyncIterable} */(source), encodePin, transfer) + : encodePin(/** @type {Pin} */(source)) + + const result = await this.remote.rmAll({ ...options, source: encodedSource, transfer }) + + yield * decodeIterable(result.data, decodeCID) + } +} +module.exports = PinClient + +/** + * + * @param {string|CID} pathOrCID + * @returns {string|EncodedCID} + */ +const encodePathOrCID = pathOrCID => + CID.isCID(pathOrCID) ? encodeCID(pathOrCID) : pathOrCID + +/** + * + * @param {Pin} pin + * @returns {EncodedPin} + */ +const encodePin = pin => ({ + ...pin, path: typeof pin.path === "string" ? pin.path : encodeCID(pin.path) +}) + +/** + * @param {EncodedLsEntry} encodedEntry + * @returns {LsEntry} + */ +const decodeLsEntry = ({ cid, metadata, type }) => { + const entry = { + cid: decodeCID(cid), + type + } + + if (metadata) { + entry.metadata = metadata + } + + return entry +} diff --git a/packages/ipfs-message-port-client/test/interface-message-port-client.js b/packages/ipfs-message-port-client/test/interface-message-port-client.js index 9381c955dd..51b906f7d7 100644 --- a/packages/ipfs-message-port-client/test/interface-message-port-client.js +++ b/packages/ipfs-message-port-client/test/interface-message-port-client.js @@ -148,11 +148,6 @@ describe('interface-ipfs-core tests', () => { name: 'should remove by CID in buffer', reason: 'Passing CID as Buffer is not supported' }, - { - name: 'should error when removing pinned blocks', - reason: 'ipfs.pin.add is not implemented' - }, - { name: 'should remove multiple CIDs', reason: 'times out' @@ -167,4 +162,59 @@ describe('interface-ipfs-core tests', () => { } ] }) + + tests.pin(factory, { + skip: [ + // pin.addAll + { + name: 'should add an array of CIDs', + reason: 'ipfs.pin.addAll is not implemented' + }, + { + name: 'should add a generator of CIDs', + reason: 'ipfs.pin.addAll is not implemented' + }, + { + name: 'should add an async generator of CIDs', + reason: 'ipfs.pin.addAll is not implemented' + }, + { + name: 'should add an array of pins with options', + reason: 'ipfs.pin.addAll is not implemented' + }, + { + name: 'should add a generator of pins with options', + reason: 'ipfs.pin.addAll is not implemented' + }, + { + name: 'should add an async generator of pins with options', + reason: 'ipfs.pin.addAll is not implemented' + }, + { + name: 'should respect timeout option when pinning a block', + reason: 'ipfs.pin.addAll is not implemented' + }, + // pin.rm + { + name: 'should respect timeout option when unpinning a block', + reason: 'ipfs.pin.rm is not implemented' + }, + { + name: 'should remove a recursive pin', + reason: 'ipfs.pin.rm is not implemented' + }, + { + name: 'should remove a direct pin', + reason: 'ipfs.pin.rm is not implemented' + }, + { + name: 'should fail to remove an indirect pin', + reason: 'ipfs.pin.rm is not implemented' + }, + { + name: 'should fail when an item is not pinned', + reason: 'ipfs.pin.rm is not implemented' + }, + ] + }) }) diff --git a/packages/ipfs-message-port-protocol/package.json b/packages/ipfs-message-port-protocol/package.json index a1e5d0bd79..526c30b375 100644 --- a/packages/ipfs-message-port-protocol/package.json +++ b/packages/ipfs-message-port-protocol/package.json @@ -1,6 +1,6 @@ { "name": "ipfs-message-port-protocol", - "version": "0.6.0", + "version": "0.7.0", "description": "IPFS client/server protocol over message port", "keywords": [ "ipfs" @@ -41,7 +41,8 @@ }, "dependencies": { "cids": "^1.1.5", - "ipld-block": "^0.11.0" + "ipld-block": "^0.11.0", + "ipfs-core-types": "^0.3.0" }, "devDependencies": { "aegir": "^30.3.0", diff --git a/packages/ipfs-message-port-protocol/src/pin.js b/packages/ipfs-message-port-protocol/src/pin.js new file mode 100644 index 0000000000..d70114feff --- /dev/null +++ b/packages/ipfs-message-port-protocol/src/pin.js @@ -0,0 +1,54 @@ +'use strict' + +/* eslint-env browser */ + +/** + * @typedef {import('ipfs-core-types').AbortOptions} AbortOptions + * @typedef {import('./cid').CID} CID + * @typedef {import('./cid').EncodedCID} EncodedCID + */ + +/** + * @template T + * @typedef {import('./core').RemoteIterable} RemoteIterable + */ + +/** + * @typedef {'direct'|'recursive'|'indirect'} PinType + * @typedef {PinType|'all'} PinQueryType + * + * @typedef {Object} Pin + * @property {string|CID} path + * @property {boolean} [recursive] + * @property {any} [metadata] + * + * @typedef {Object} EncodedPin + * @property {string|EncodedCID} path + * @property {boolean} [recursive] + * @property {any} [metadata] + * + * @typedef {Pin|AsyncIterable} Source + * @typedef {EncodedPin|RemoteIterable} EncodedSource + */ + +/** + * @typedef {LsSettings & AbortOptions} LsOptions + * + * @typedef {Object} LsSettings + * @property {Array|string|CID} [paths] + * @property {number} [timeout] + * @property {PinType} [type] + * + * @typedef {Object} LsEntry + * @property {CID} cid + * @property {PinType} type + * @property {any} [metadata] + * + * @typedef {Object} EncodedLsEntry + * @property {EncodedCID} cid + * @property {PinType} type + * @property {any} [metadata] + */ + +// trigger type exports +exports.default = {} diff --git a/packages/ipfs-message-port-server/package.json b/packages/ipfs-message-port-server/package.json index d87e29da1f..68c2ef828b 100644 --- a/packages/ipfs-message-port-server/package.json +++ b/packages/ipfs-message-port-server/package.json @@ -43,7 +43,7 @@ "dep-check": "aegir dep-check -i rimraf" }, "dependencies": { - "ipfs-message-port-protocol": "^0.6.0", + "ipfs-message-port-protocol": "file:../ipfs-message-port-protocol", "it-all": "^1.0.4" }, "devDependencies": { diff --git a/packages/ipfs-message-port-server/src/index.js b/packages/ipfs-message-port-server/src/index.js index 119aa05894..647f179fd3 100644 --- a/packages/ipfs-message-port-server/src/index.js +++ b/packages/ipfs-message-port-server/src/index.js @@ -16,5 +16,8 @@ exports.BlockService = BlockService const { IPFSService } = require('./service') exports.IPFSService = IPFSService +const { PinService } = require('./pin') +exports.PinService = PinService + const { Server } = require('./server') exports.Server = Server diff --git a/packages/ipfs-message-port-server/src/ipfs.ts b/packages/ipfs-message-port-server/src/ipfs.ts index 765a4087e5..9be5e9fba0 100644 --- a/packages/ipfs-message-port-server/src/ipfs.ts +++ b/packages/ipfs-message-port-server/src/ipfs.ts @@ -1,4 +1,5 @@ import { DAGNode } from 'ipfs-message-port-protocol/src/dag' +import * as pin from 'ipfs-message-port-protocol/src/pin' import CID from 'cids' import { FileType, @@ -14,6 +15,7 @@ export interface IPFS extends Core { dag: DAG files: Files block: BlockService + pin: PinService } export interface IPFSFactory { @@ -62,6 +64,12 @@ export interface Core { ls: (ipfsPath: CID | string, options: CoreLsOptions) => AsyncIterable } +export interface PinService { + add(path: string | CID, options?: AddPinOptions): Promise + ls(options: pin.LsOptions): AsyncIterable + rmAll(source: pin.Source, options?: AbortOptions): AsyncIterable +} + export interface AddOptions extends AbortOptions { chunker?: string cidVersion?: number @@ -76,6 +84,10 @@ export interface AddOptions extends AbortOptions { wrapWithDirectory?: boolean } +export interface AddPinOptions extends AbortOptions { + recursive?: boolean +} + export interface FileInput { path?: string content?: FileContent diff --git a/packages/ipfs-message-port-server/src/pin.js b/packages/ipfs-message-port-server/src/pin.js new file mode 100644 index 0000000000..96beb67875 --- /dev/null +++ b/packages/ipfs-message-port-server/src/pin.js @@ -0,0 +1,170 @@ +'use strict' + +/* eslint-env browser */ + +const { CID, decodeCID, encodeCID } = require('ipfs-message-port-protocol/src/cid') +const { decodeIterable, encodeIterable } = require('ipfs-message-port-protocol/src/core') + +/** + * @typedef {import('ipfs-message-port-protocol/src/cid').EncodedCID} EncodedCID + * @typedef {import('ipfs-message-port-protocol/src/pin').EncodedLsEntry} EncodedLsEntry + * @typedef {import('ipfs-message-port-protocol/src/pin').EncodedPin} EncodedPin + * @typedef {import('ipfs-message-port-protocol/src/pin').EncodedSource} EncodedSource + * @typedef {import('ipfs-message-port-protocol/src/pin').LsEntry} LsEntry + * @typedef {import('ipfs-message-port-protocol/src/pin').Pin} Pin + * @typedef {import('ipfs-message-port-protocol/src/pin').PinType} PinType + * @typedef {import('ipfs-message-port-protocol/src/pin').PinQueryType} PinQueryType + * @typedef {import('ipfs-message-port-protocol/src/pin').Source} Source + * @typedef {import('./ipfs').IPFS} IPFS + */ + +/** + * @template T + * @typedef {import('ipfs-message-port-protocol/src/core').RemoteIterable} RemoteIterable + */ + +exports.PinService = class PinService { + /** + * + * @param {IPFS} ipfs + */ + constructor (ipfs) { + this.ipfs = ipfs + } + + /** + * @typedef {Object} PinQuery + * @property {string|EncodedCID} path + * @property {boolean} [recursive=true] + * @property {number} [timeout] + * @property {AbortSignal} [signal] + * + * @typedef {Object} PinResult + * @property {EncodedCID} cid + * @property {Transferable[]} transfer + * + * @param {PinQuery} input + * @returns {Promise} + */ + async add (input) { + const cid = await this.ipfs.pin.add(decodePathOrCID(input.path), input) + /** @type {Transferable[]} */ + const transfer = [] + return { cid: encodeCID(cid, transfer), transfer } + } + + /** + * @typedef {Object} LsQuery + * @property {Array|string|EncodedCID} [paths] + * @property {AbortSignal} [signal] + * @property {number} [timeout] + * @property {PinType} [type] + * + * @typedef {Object} LsResult + * @property {RemoteIterable} data + * @property {Transferable[]} transfer + * + * @param {LsQuery} query + * @returns {LsResult} + */ + ls (query) { + const { paths, signal, timeout, type } = query + let decodedPaths + + if (paths === undefined) { + decodedPaths = undefined + } else if (Array.isArray(paths)) { + decodedPaths = [] + paths.forEach(path => decodedPaths.push( + typeof path === 'string' ? path : decodeCID(path) + )) + } else if (typeof paths === 'string') { + decodedPaths = paths + } else { + decodedPaths = decodeCID(paths) + } + + const result = this.ipfs.pin.ls({ paths: decodedPaths, signal, timeout, type }) + return encodeLsResult(result) + } + + /** + * @typedef {Object} RmAllQuery + * @property {EncodedSource} source + * @property {AbortSignal} [signal] + * @property {number} [timeout] + * + * @typedef {Object} RmAllResult + * @property {RemoteIterable} data + * @property {Transferable[]} transfer + * + * @param {RmAllQuery} query + * @returns {RmAllResult} + */ + rmAll (query) { + const { signal, source, timeout } = query + const decodedSource = /** @type {RemoteIterable} */(source).port + ? decodeIterable(/** @type {RemoteIterable} */(source), decodePin) + : decodePin(/** @type {EncodedPin} */(source)) + + const result = this.ipfs.pin.rmAll(decodedSource, { signal, timeout }) + return encodeRmAllResult(result) + } +} + +/** + * + * @param {string|EncodedCID} pathOrCID + * @returns {string|CID} + */ +const decodePathOrCID = pathOrCID => + typeof pathOrCID === "string" ? pathOrCID : decodeCID(pathOrCID) + +/** + * + * @param {EncodedPin} pin + * @returns {Pin} + */ +const decodePin = pin => { + return { ...pin, path: decodePathOrCID(pin.path) } +} + +/** + * + * @param {AsyncIterable} entries + * @returns {LsResult} + */ +const encodeLsResult = entries => { + /** @type {Transferable[]} */ + const transfer = [] + return { data: encodeIterable(entries, encodeLsEntry, transfer), transfer } +} + +/** + * + * @param {AsyncIterable} entries + * @returns {RmAllResult} + */ +const encodeRmAllResult = entries => { + /** @type {Transferable[]} */ + const transfer = [] + return { data: encodeIterable(entries, encodeCID, transfer), transfer } +} + +/** + * + * @param {LsEntry} entry + * @returns {EncodedLsEntry} + */ +const encodeLsEntry = ({ cid, metadata, type }, transfer) => { + const entry = { + cid: encodeCID(cid, transfer), + type + } + + if (metadata) { + entry.metadata = metadata + } + + return entry +} diff --git a/packages/ipfs-message-port-server/src/service.js b/packages/ipfs-message-port-server/src/service.js index 5c48f3836b..77068ad2eb 100644 --- a/packages/ipfs-message-port-server/src/service.js +++ b/packages/ipfs-message-port-server/src/service.js @@ -2,10 +2,11 @@ /* eslint-env browser */ -const { DAGService } = require('./dag') +const { BlockService } = require('./block') const { CoreService } = require('./core') +const { DAGService } = require('./dag') const { FilesService } = require('./files') -const { BlockService } = require('./block') +const { PinService } = require('./pin') /** * @typedef {import('./ipfs').IPFS} IPFS @@ -17,9 +18,10 @@ exports.IPFSService = class IPFSService { * @param {IPFS} ipfs */ constructor (ipfs) { - this.dag = new DAGService(ipfs) + this.block = new BlockService(ipfs) this.core = new CoreService(ipfs) + this.dag = new DAGService(ipfs) this.files = new FilesService(ipfs) - this.block = new BlockService(ipfs) + this.pin = new PinService(ipfs) } }