@co2-storage/js-api
Version:
JS API for co2.storage
1,602 lines (1,467 loc) • 94.1 kB
JavaScript
import { create as createClient } from 'ipfs-http-client'
import { CID } from 'multiformats/cid'
import multihash from 'multihashes'
import { UnixFS } from 'ipfs-unixfs'
import { CommonHelpers } from '../helpers/Common.js'
import { FGHelpers } from '../helpers/FG.js'
import { EstuaryHelpers } from '../helpers/Estuary.js'
import { Auth } from '../auth/Auth.js'
import { signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util'
import { multiaddr } from '@multiformats/multiaddr'
import { webSockets } from '@libp2p/websockets'
import { isBrowser, isNode } from "browser-or-node"
const ws = new webSockets()
export class FGStorage {
peers = [
'/dns4/web1.co2.storage/tcp/5004/wss/p2p/12D3KooWCPzmui9TSQQG8HTNcZeFiHz6AGS19aaCwxJdjykVqq7f',
'/dns4/web2.co2.storage/tcp/5004/wss/p2p/12D3KooWFBCcWEDW9GYr9Aw8D2QL7hZakPAw1DGfeZCwfsrjd43b',
'/dns4/green.filecoin.space/tcp/5004/wss/p2p/12D3KooWJmYbQp2sgKX22vZgSRVURkpMQ5YCSc8vf3toHesJc5Y9',
'/dns4/proxy.co2.storage/tcp/5004/wss/p2p/12D3KooWGWHSrAxr6sznTpdcGuqz6zfQ2Y43PZQzhg22uJmGP9n1',
/* '/dns4/node0.preload.ipfs.io/tcp/443/wss/p2p/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',
'/dns4/node1.preload.ipfs.io/tcp/443/wss/p2p/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6',
'/dns4/node2.preload.ipfs.io/tcp/443/wss/p2p/QmV7gnbW5VTcJ3oyM2Xk1rdFBJ3kTkvxc87UFGsun29STS',
'/dns4/node3.preload.ipfs.io/tcp/443/wss/p2p/QmY7JB6MQXhxHvq7dBDh4HpbH29v4yE9JRadAVpndvzySN',*/
]
// ipfsRepoName = './ipfs_repo_' + Math.random()
ipfsRepoName = './.ipfs'
ipfsNodeAddr = (process.env.NODE_ENV == 'production') ? '/dns4/web1.co2.storage/tcp/5002/https' : '/ip4/127.0.0.1/tcp/5001'
ipfsNodeType = 'client'
ipfsNodeOpts = {
config: {},
libp2p: {
transports: [ws],
connectionManager: {
autoDial: false
}
}
}
selectedAddress = null
ipfs = null
ipfsStarting = false
ipfsStarted = false
commonHelpers = null
fgHelpers = null
estuaryHelpers = null
authType = null
auth = null
fgApiHost = (process.env.NODE_ENV == 'production') ? "https://co2.storage" : "http://localhost:3020"
fgApiToken = null
estuaryApiHost = "https://api.estuary.tech"
verifyingCidSignatureContractABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"geteip712DomainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"string","name":"cid","type":"string"}],"name":"gethashStruct","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"string","name":"cid","type":"string"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"verifySignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]
verifyingCidSignatureContractAddress = "0x7c75AA9001c4E35EDfb5466d3fdBdd3729dd4Ee7"
verifyingMessageSignatureContractABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"geteip712DomainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"string","name":"message","type":"string"}],"name":"gethashStruct","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"string","name":"message","type":"string"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"verifySignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]
verifyingMessageSignatureContractAddress = "0x3cF60d94C95965D20E904e28fCf2DD0a14DB384f"
ethereumChainId = 1
constructor(options) {
if(options.authType != undefined)
this.authType = options.authType
if(options.ipfsNodeType != undefined)
this.ipfsNodeType = options.ipfsNodeType
if(options.ipfsRepoName != undefined)
this.ipfsRepoName = options.ipfsRepoName
if(options.ipfsNodeOpts != undefined)
this.ipfsNodeOpts = Object.assign(this.ipfsNodeOpts, options.ipfsNodeOpts)
if(options.ipfsNodeAddr != undefined)
this.ipfsNodeAddr = options.ipfsNodeAddr
if(options.fgApiHost != undefined)
this.fgApiHost = options.fgApiHost
if(options.fgApiToken != undefined)
this.fgApiToken = options.fgApiToken
this.commonHelpers = new CommonHelpers()
this.fgHelpers = new FGHelpers()
this.estuaryHelpers = new EstuaryHelpers()
this.auth = new Auth(this.authType)
}
async authenticate() {
const authResponse = await this.auth.authenticate()
if(authResponse.error != null) {
return {
result: null,
error: authResponse.error
}
}
return {
result: authResponse.result,
error: null,
web3: authResponse.web3
}
}
async destroy() {
await this.ipfs.stop()
}
async startIpfs() {
const that = this
this.ipfsStarting = true
this.ipfsStarted = false
let ipfsOpts = {}
switch (this.ipfsNodeType) {
case 'client':
if(!this.fgApiToken)
await this.getApiToken()
ipfsOpts = Object.assign({url: this.ipfsNodeAddr, timeout: '1w', headers: {'Authorization': btoa(this.fgApiToken)}}, this.ipfsNodeOpts)
// ipfsOpts = Object.assign({url: this.ipfsNodeAddr, timeout: '1w'}, this.ipfsNodeOpts)
this.ipfs = await createClient(ipfsOpts)
break
case 'browser':
if(!window)
return
ipfsOpts = Object.assign({
repo: this.ipfsRepoName,
EXPERIMENTAL: {
ipnsPubsub: true
}
}, this.ipfsNodeOpts)
this.ipfs = await window.IpfsCore.create(ipfsOpts)
break
default:
ipfsOpts = Object.assign({url: this.ipfsNodeAddr, timeout: '1w'}, this.ipfsNodeOpts)
this.ipfs = await createClient(ipfsOpts)
break
}
const config = await this.ipfs.config.getAll()
const hostPeerId = config.Identity.PeerID
for (const peer of this.peers) {
if(peer.indexOf(hostPeerId) == -1)
try {
const ma = multiaddr(peer)
await this.ipfs.bootstrap.add(ma)
await this.ipfs.swarm.connect(ma)
} catch (error) {
console.log(peer, error)
}
}
this.ipfsStarted = true
this.ipfsStarting = false
return this.ipfs
}
async stopIpfs() {
if(this.ipfs != null) {
await this.ipfs.stop()
this.ipfs = null
this.ipfsStarted = false
this.ipfsStarting = false
}
}
async getDag(cid) {
let response
try {
await this.ensureIpfsIsRunning()
response = (await this.ipfs.dag.get(CID.parse(cid))).value
} catch (error) {
return null
}
return response
}
async getApiToken(issueNewToken) {
const authResponse = await this.authenticate()
if(authResponse.error != null)
return new Promise((resolve, reject) => {
reject({
result: null,
error: authResponse.error
})
})
this.selectedAddress = authResponse.result
let signedTokenRequest
try {
signedTokenRequest = (await this.signMessage(`Filecoin Green token request made on ${(new Date()).toISOString()}`)).result
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
let result
try {
result = (await this.fgHelpers.signup(this.fgApiHost, signedTokenRequest, (issueNewToken == true))).result
this.fgApiToken = result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
error: null,
result: result
})
})
}
async setApiToken(token) {
const authResponse = await this.authenticate()
if(authResponse.error != null)
return new Promise((resolve, reject) => {
reject({
result: null,
error: authResponse.error
})
})
this.selectedAddress = authResponse.result
this.fgApiToken = token
}
async checkApiTokenValidity(token) {
const authResponse = await this.authenticate()
if(authResponse.error != null)
return new Promise((resolve, reject) => {
reject({
result: null,
error: authResponse.error
})
})
this.selectedAddress = authResponse.result
let response, result = false
try {
response = (await this.fgHelpers.authenticate(this.fgApiHost, token)).result
const validity = new Date(response.data.validity)
if(validity > new Date())
result = true
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: false
})
})
}
return new Promise((resolve, reject) => {
resolve({
error: null,
result: result
})
})
}
async getApiProfile() {
const authResponse = await this.authenticate()
if(authResponse.error != null)
return new Promise((resolve, reject) => {
reject({
result: null,
error: authResponse.error
})
})
this.selectedAddress = authResponse.result
if(this.fgApiToken == undefined)
try {
this.fgApiToken = (await this.getApiToken(true)).result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let result
try {
result = (await this.fgHelpers.authenticate(this.fgApiHost, this.fgApiToken)).result
this.fgApiToken = result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
error: null,
result: result
})
})
}
async ensureIpfsIsRunning() {
if(!this.ipfsStarted && !this.ipfsStarting) {
this.ipfs = await this.startIpfs()
}
else if(!this.ipfsStarted) {
while(!this.ipfsStarted) {
await this.commonHelpers.sleep(1000)
}
}
return this.ipfs
}
async getAccounts(chainName) {
const that = this
let walletChain = {}, walletsChain = {}
let walletsCid = null, head = null
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
const authResponse = await this.authenticate()
if(authResponse.error != null)
return new Promise((resolve, reject) => {
reject({
result: null,
error: authResponse.error
})
})
this.selectedAddress = authResponse.result
if(this.fgApiToken == undefined)
try {
this.fgApiToken = (await this.getApiToken(true)).result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
try {
head = (await this.fgHelpers.head(this.fgApiHost, chainName)).result
} catch (headResponse) {
if(headResponse.error.response.status != 404) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
head = null
}
if(head == null) {
// Create genesis block
walletChain = {
"parent": null,
"version": this.commonHelpers.walletVersion,
"name": null,
"description": null,
"timestamp": (new Date()).toISOString(),
"wallet": this.selectedAddress,
"templates": [],
"assets": [],
"provenance": [],
"functions": [],
"pipelines": []
}
const walletChainCid = await this.ipfs.dag.put(walletChain, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cidw
try {
cidw = (await this.fgHelpers.addCborDag(this.fgApiHost, walletChain, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(walletChainCid.toString() != cidw)
await this.ipfs.pin.add(CID.parse(cidw))
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", cidw, `wallet_chain_${that.selectedAddress}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", cidw, `wallet_chain_${that.selectedAddress}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
walletsChain["parent"] = null
walletsChain["timestamp"] = (new Date()).toISOString()
walletsChain["version"] = this.commonHelpers.walletsVersion
walletsChain[this.selectedAddress] = cidw
}
else {
// Retrieve last block / head
walletsCid = head.data.head
// Get last walletsChain block
walletsChain = (await this.ipfs.dag.get(CID.parse(walletsCid))).value
// Check is this account already added to accounts
if(walletsChain[this.selectedAddress] == null) {
// Add this account
walletChain = {
"parent": null,
"version": this.commonHelpers.walletVersion,
"name": null,
"description": null,
"timestamp": (new Date()).toISOString(),
"wallet": this.selectedAddress,
"templates": [],
"assets": [],
"provenance": [],
"functions": [],
"pipelines": []
}
const walletChainCid = await this.ipfs.dag.put(walletChain, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cidw
try {
cidw = (await this.fgHelpers.addCborDag(this.fgApiHost, walletChain, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(walletChainCid.toString() != cidw)
await this.ipfs.pin.add(CID.parse(cidw))
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", cidw, `wallet_chain_${that.selectedAddress}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", cidw, `wallet_chain_${that.selectedAddress}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
walletsChain["parent"] = walletsCid
walletsChain["timestamp"] = (new Date()).toISOString()
walletsChain["version"] = this.commonHelpers.walletsVersion
walletsChain[this.selectedAddress] = cidw
}
}
const walletsChainCid = await this.ipfs.dag.put(walletsChain, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cidws
try {
cidws = (await this.fgHelpers.addCborDag(this.fgApiHost, walletsChain, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(walletsChainCid.toString() != cidws)
await this.ipfs.pin.add(CID.parse(cidws))
// Update head record (and signup for a token if needed)
if(cidws != walletsCid) {
try {
await this.fgHelpers.updateHead(chainName, this.fgApiHost, walletsChain["parent"], cidws, this.selectedAddress, this.fgApiToken)
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
}
walletsCid = cidws
return new Promise((resolve, reject) => {
resolve({
result: {
list: walletsChain,
cid: walletsCid
},
error: null
})
})
}
async getAccount(chainName) {
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let accounts
try {
accounts = await this.getAccounts(chainName)
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
const accountCid = accounts.result.list[this.selectedAddress]
const value = (await this.ipfs.dag.get(CID.parse(accountCid))).value
const walletsCid = accounts.result.cid
const list = accounts.result.list
return new Promise((resolve, reject) => {
resolve({
result: {
accounts: {
cid: walletsCid,
list: list
},
value: value,
cid: accountCid
},
error: null
})
})
}
async searchTemplates(chainName, phrases, cid, name, base, account, offset, limit, sortBy, sortDir, or) {
let templates = [], total = 0
if(offset == undefined)
offset = 0
if(limit == undefined)
limit = 10
try {
const myTemplates = (await this.search(chainName, phrases, 'template', cid, null, name, null, base, null, null, account, null, null, null, null, null, offset, limit, sortBy, sortDir, or)).result
templates = myTemplates.map((template) => {
return {
template: template,
block: template.cid,
cid: template.content_cid
}
})
total = (templates.length) ? templates[0].template.total : 0
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
result: {
templates: templates,
offset: offset,
limit: limit,
total: total
},
error: null
})
})
}
async addTemplate(template, name, base, description, parent, chainName) {
const that = this
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(this.fgApiToken == undefined)
try {
this.fgApiToken = (await this.getApiToken(true)).result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let account
try {
account = await this.getAccount(chainName)
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
let templates = account.result.value.templates
// Search for template / schema types in provided template
if(Array.isArray(template)) {
// Template is a list
if(Array.isArray(template[0])) {
// Template is a list of lists
let templateVals = template.map((el)=>{return el[1]})
for (let templateVal of templateVals) {
const templateType = templateVal['type']
const subTemplate = templateVal['value']
if(templateType.toLowerCase() == 'template' || templateType.toLowerCase() == 'schema' || templateType.toLowerCase() == 'template-list' || templateType.toLowerCase() == 'schema-list') {
const subTemplateCid = this.makeCid(subTemplate)
if(subTemplateCid == null)
return new Promise((resolve, reject) => {
reject({
error: `Provided template CID ${subTemplate} is invalid.`,
result: null
})
})
templateVal['value'] = subTemplateCid
}
}
}
else {
// Template is a list of objects
let templateVals = template.map((el)=>{return el[Object.keys(el)[0]]})
for (let templateVal of templateVals) {
const templateType = templateVal['type']
const subTemplate = templateVal['value']
if(templateType.toLowerCase() == 'template' || templateType.toLowerCase() == 'schema' || templateType.toLowerCase() == 'template-list' || templateType.toLowerCase() == 'schema-list') {
const subTemplateCid = this.makeCid(subTemplate)
if(subTemplateCid == null)
return new Promise((resolve, reject) => {
reject({
error: `Provided template CID ${subTemplate} is invalid.`,
result: null
})
})
templateVal['value'] = subTemplateCid
}
}
}
}
else {
// Template is an object
const templateKeys = Object.keys(template)
for (const templateKey of templateKeys) {
const templateKeyType = template[templateKey]['type']
const subTemplate = template[templateKey]['value']
if(templateKeyType.toLowerCase() == 'template' || templateKeyType.toLowerCase() == 'schema' || templateKeyType.toLowerCase() == 'template-list' || templateKeyType.toLowerCase() == 'schema-list') {
const subTemplateCid = this.makeCid(subTemplate)
if(subTemplateCid == null)
return new Promise((resolve, reject) => {
reject({
error: `Provided template CID ${subTemplate} is invalid.`,
result: null
})
})
template[templateKey]['value'] = subTemplateCid
}
}
}
const templateCid = await this.ipfs.dag.put(template, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cidt
try {
cidt = (await this.fgHelpers.addCborDag(this.fgApiHost, template, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(templateCid.toString() != cidt)
await this.ipfs.pin.add(CID.parse(cidt))
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", cidt, `template_${name}_${cidt}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", cidt, `template_${name}_${cidt}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
const templateBlock = {
"parent": (parent) ? parent : null,
"timestamp": (new Date()).toISOString(),
"version": this.commonHelpers.templateBlockVersion,
"creator": this.selectedAddress,
"cid": cidt,
"name": name,
"base": (base && base.title) ? base.title : null,
"reference": (base && base.reference) ? base.reference : null,
"description": description,
"protocol_name" : "transform.storage",
"type_checking": this.commonHelpers.typeChecking
}
const templateBlockCid = await this.ipfs.dag.put(templateBlock, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cidtb
try {
cidtb = (await this.fgHelpers.addCborDag(this.fgApiHost, templateBlock, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(templateBlockCid.toString() != cidtb)
await this.ipfs.pin.add(CID.parse(cidtb))
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", cidtb, `template_block_${name}_${cidtb}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", cidtb, `template_block_${name}_${cidtb}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
templates.push(cidtb)
try {
await this.updateAccount(null, templates, null, null, null, chainName)
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
error: null,
result: {
templateBlock: templateBlock,
block: cidtb,
template: template
}
})
})
}
async getTemplate(templateBlockCid) {
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let templateBlock, template
try {
templateBlockCid = CID.parse(templateBlockCid)
templateBlock = (await this.ipfs.dag.get(templateBlockCid)).value
template = (await this.ipfs.dag.get(CID.parse(templateBlock.cid))).value
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
result: {
block: templateBlockCid.toString(),
templateBlock: templateBlock,
template: template
},
error: null
})
})
}
async searchAssets(chainName, phrases, cid, name, base, account, offset, limit, sortBy, sortDir, or) {
let assets = [], total = 0
if(offset == undefined)
offset = 0
if(limit == undefined)
limit = 10
try {
const myAssets = (await this.search(chainName, phrases, 'asset', cid, null, name, null, base, null, null, account, null, null, null, null, null, offset, limit, sortBy, sortDir, or)).result
assets = myAssets.map((asset) => {
return {
asset: asset,
block: asset.cid,
cid: asset.content_cid
}
})
total = (assets.length) ? assets[0].asset.total : 0
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
result: {
assets: assets,
offset: offset,
limit: limit,
total: total
},
error: null
})
})
}
async addAsset(assetElements, parameters, chainName, uploadCallback) {
const that = this
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(this.fgApiToken == undefined)
try {
this.fgApiToken = (await this.getApiToken(true)).result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let account
try {
account = await this.getAccount(chainName)
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
let assets = account.result.value.assets
// Map asset elements with provided template
let template
try {
const templateBlockCid = CID.parse(parameters.template)
const templateBlock = (await this.ipfs.dag.get(templateBlockCid)).value
template = (await this.ipfs.dag.get(CID.parse(templateBlock.cid))).value
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
// Prepare non trivial asset elements to be stored on IPFS
let prepareAsset = await this._prepareAssetElements(assetElements, template, parameters, uploadCallback)
if(prepareAsset.error != null)
return new Promise((resolve, reject) => {
reject({
error: prepareAsset.error,
result: null
})
})
assetElements = prepareAsset.assetElements
if (parameters.hasOwnProperty('createAssetStart') && typeof parameters.createAssetStart == 'function')
parameters.createAssetStart()
// Create asset data structure
const asset = this._createAssetDataStructure(assetElements)
const assetCid = await this.ipfs.dag.put(asset, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cida
try {
cida = (await this.fgHelpers.addCborDag(this.fgApiHost, asset, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
if(assetCid.toString() != cida)
await this.ipfs.pin.add(CID.parse(cida))
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", cida, `asset_${parameters.name}_${cida}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", cida, `asset_${parameters.name}_${cida}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
const assetBlock = {
"parent": parameters.parent,
"timestamp": (new Date()).toISOString(),
"version": this.commonHelpers.assetBlockVersion,
"creator": this.selectedAddress,
"cid": cida,
"name": parameters.name,
"description": parameters.description,
"template": parameters.template,
"protocol_name" : "transform.storage"
}
const assetBlockCid = await this.ipfs.dag.put(assetBlock, {
storeCodec: 'dag-cbor',
hashAlg: 'sha2-256',
pin: true
})
let cidab
try {
cidab = (await this.fgHelpers.addCborDag(this.fgApiHost, assetBlock, this.fgApiToken)).result.data.cid
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
if(assetBlockCid.toString() != cidab)
await this.ipfs.pin.add(CID.parse(cidab))
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", cidab, `asset_block_${parameters.name}_${cidab}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", cidab, `asset_block_${parameters.name}_${cidab}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
assets.push(cidab)
try {
await this.updateAccount(assets, null, null, null, null, chainName)
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
if (parameters.hasOwnProperty('createAssetEnd') && typeof parameters.createAssetEnd == 'function')
parameters.createAssetEnd()
return new Promise((resolve, reject) => {
resolve({
error: null,
result: {
assetBlock: assetBlock,
block: cidab,
asset: asset
}
})
})
}
_createAssetDataStructure(assetElements) {
let asset = assetElements
.filter((f) => {
return f && Object.keys(f).length > 0 && Object.getPrototypeOf(f) === Object.prototype
})
.map((f) => {
if((f.type == 'schema' || f.type == 'schema-list' || f.type == 'template' || f.type == 'template-list') && typeof f.value == 'object') {
f.value = this._createAssetDataStructure(f.value)
}
return {
[f.name] : f.value
}
})
return asset
}
_determineTemplateTypeAndRetrieveKeys(template) {
let templateKeys = []
let templateType = 'object'
if(Array.isArray(template)) {
// Template is a list
if(Array.isArray(template[0])) {
// Template is a list of lists
templateKeys = template.map((el)=>{return el[0]})
templateType = 'list_of_lists'
}
else {
// Template is a list of objects
templateKeys = template.map((el)=>{return Object.keys(el)[0]})
templateType = 'list_of_objects'
}
}
else {
// Template is an object
templateKeys = Object.keys(template)
}
return {
templateType: templateType,
templateKeys: templateKeys
}
}
async _assignTypesToAssetElements(assetElements, template) {
let result
const templateTypeAndKeys = this._determineTemplateTypeAndRetrieveKeys(template)
const templateType = templateTypeAndKeys.templateType
const templateKeys = templateTypeAndKeys.templateKeys
console.log(2, assetElements)
for (let assetElement of assetElements) {
const key = assetElement.name
const index = templateKeys.indexOf(key)
if(index == -1) {
return {
assetElements: null,
error: "Provided asset is not matching with a template."
}
}
switch (templateType) {
case 'list_of_lists':
case 'list_of_objects':
if(templateType == 'list_of_lists') {
assetElement.type = template[index][1].type
if((assetElement.type == 'schema' || assetElement.type == 'schema-list' || assetElement.type == 'template' || assetElement.type == 'template-list') && typeof assetElement.value == 'object') {
template = await this._extractNestedTemplates(template, true)
result = await this._assignTypesToAssetElements(assetElement.value, template[index][1].value)
}
else if((assetElement.type == 'schema' || assetElement.type == 'schema-list' || assetElement.type == 'template' || assetElement.type == 'template-list') && typeof assetElement.value == 'string') {
try {
const subAssetCid = await this._validateAssetElementCid(assetElement.value)
assetElement.value = await this._resolveAssetElementCid(subAssetCid)
template = await this._extractNestedTemplates(template, true)
result = await this._assignTypesToAssetElements(assetElement.value, template[index][1].value)
} catch (error) {
return {
assetElements: null,
error: error
}
}
}
}
else if(templateType == 'list_of_objects') {
assetElement.type = template[index][key].type
if((assetElement.type == 'schema' || assetElement.type == 'schema-list' || assetElement.type == 'template' || assetElement.type == 'template-list') && typeof assetElement.value == 'object') {
template = await this._extractNestedTemplates(template, true)
result = await this._assignTypesToAssetElements(assetElement.value, template[index][key].value)
}
else if((assetElement.type == 'schema' || assetElement.type == 'schema-list' || assetElement.type == 'template' || assetElement.type == 'template-list') && typeof assetElement.value == 'string') {
try {
const subAssetCid = await this._validateAssetElementCid(assetElement.value)
assetElement.value = await this._resolveAssetElementCid(subAssetCid)
template = await this._extractNestedTemplates(template, true)
result = await this._assignTypesToAssetElements(assetElement.value, template[index][key].value)
} catch (error) {
return {
assetElements: null,
error: error
}
}
}
}
break
default:
assetElement.type = template[key].type
if((assetElement.type == 'schema' || assetElement.type == 'schema-list' || assetElement.type == 'template' || assetElement.type == 'template-list') && typeof assetElement.value == 'object') {
template = await this._extractNestedTemplates(template, true)
result = await this._assignTypesToAssetElements(assetElement.value, template[key].value)
}
else if((assetElement.type == 'schema' || assetElement.type == 'schema-list' || assetElement.type == 'template' || assetElement.type == 'template-list') && typeof assetElement.value == 'string') {
try {
const subAssetCid = await this._validateAssetElementCid(assetElement.value)
assetElement.value = await this._resolveAssetElementCid(subAssetCid)
template = await this._extractNestedTemplates(template, true)
result = await this._assignTypesToAssetElements(assetElement.value, template[key].value)
} catch (error) {
return {
assetElements: null,
error: error
}
}
}
}
}
result = {
assetElements: assetElements,
error: null
}
return result
}
async _validateAssetElementCid(scid) {
const cidObj = await this.makeCid(scid)
if(cidObj == null)
throw new Error(`Provided sub-asset CID ${scid.toString()} is not valid.`)
return cidObj
}
async _resolveAssetElementCid(cid) {
let result = []
const cidVal = await this.getDag(cid.toString())
if(cidVal == null)
throw new Error(`${cid.toString()} is invalid DAG structure.`)
for (const el of cidVal) {
const keys = Object.keys(el)
if(!keys.length)
throw new Error(`${cid.toString()} is invalid asset DAG structure.`)
const element = {
"name": keys[0],
"value": el[keys[0]]
}
result.push(element)
}
return result
}
async _prepareAssetElements(assetElements, template, parameters, uploadCallback) {
const that = this
let typesAssignment = await this._assignTypesToAssetElements(assetElements, template)
if(typesAssignment.error != null)
return {
assetElements: null,
error: typesAssignment.error
}
// If we have field types Schame or SchemaList
let schemaContainingElements = assetElements
.filter((f) => {return f.type == 'schema' || f.type == 'schema-list' || f.type == 'template' || f.type == 'template-list'})
for (let schemaContainingElement of schemaContainingElements) {
// Treat subforms
const templateTypeAndKeys = this._determineTemplateTypeAndRetrieveKeys(template)
const templateType = templateTypeAndKeys.templateType
const templateKeys = templateTypeAndKeys.templateKeys
const key = schemaContainingElement.name
let subTemplate
switch (templateType) {
case 'list_of_lists':
case 'list_of_objects':
const index = templateKeys.indexOf(key)
if(index == -1)
continue
if(templateType == 'list_of_lists') {
subTemplate = template[index][1].value
}
else if(templateType == 'list_of_objects') {
subTemplate = template[index][key].value
}
break
default:
if(template[key] == undefined)
continue
subTemplate = template[key].value
}
let prepareSubAsset = await this._prepareAssetElements(schemaContainingElement.value, subTemplate, parameters, uploadCallback)
if(prepareSubAsset.error != null)
return new Promise((resolve, reject) => {
reject({
error: prepareSubAsset.error,
result: null
})
})
schemaContainingElement.value = prepareSubAsset.assetElements
}
// If we have field types Image or Documents
// add them to IPFS first and remap values with CIDs
let fileContainingElements = assetElements
.filter((f) => {return f.type == 'images' || f.type == 'documents'})
if (fileContainingElements.length)
if (parameters.hasOwnProperty('filesUploadStart') && typeof parameters.filesUploadStart == 'function')
parameters.filesUploadStart()
for (const fileContainingElement of fileContainingElements) {
if(fileContainingElement.value == null)
continue
let newValue = [], folder
let results = []
for await (let file of fileContainingElement.value) {
if(isNode) {
// NodeJS environment
let cid
if(file.content) {
cid = await this.commonHelpers.addFileUsingReadStream(file.content, file.path, this.ipfs, uploadCallback)
}
else if(file.cid) {
cid = CID.parse(file.cid)
}
else {
return {
assetElements: null,
error: `Invalid file provided ${file.path}.`
}
}
results.push({
cid: cid.toString(),
path: file.path,
size: (await this.ipfs.object.stat(cid)).CumulativeSize
})
}
else if(isBrowser) {
// Browser environment
let cid
if(file.content) {
file = (file.content instanceof File) ? file.content : new File(file.content, file.path)
cid = await this.commonHelpers.addFileUsingFileReader(file, this.ipfs, uploadCallback)
}
else if(file.cid) {
cid = CID.parse(file.cid)
}
else {
return {
assetElements: null,
error: `Invalid file provided ${file.path}.`
}
}
results.push({
cid: cid.toString(),
path: file.name || file.path,
size: (await this.ipfs.object.stat(cid)).CumulativeSize
})
}
else {
// Unknown
return {
assetElements: null,
error: "Unknown environment. Expected NodeJS or Browser."
}
}
}
for (const result of results) {
if(result.path != '')
newValue.push(result)
setTimeout(async () => {
try {
await that.fgHelpers.queuePin(that.fgApiHost, "filecoin-green", result.cid.toString(), `file_${result.path}_${result.cid.toString()}`, that.selectedAddress, that.fgApiToken)
await that.fgHelpers.queuePin(that.fgApiHost, "estuary", result.cid.toString(), `file_${result.path}_${result.cid.toString()}`, that.selectedAddress, that.fgApiToken)
} catch (error) {
console.log(error)
}
}, 0)
}
// Map CIDs to asset data structure
fileContainingElement.value = newValue.map((x) => {
if (folder) x.folder = folder
return x
})
}
if (parameters.hasOwnProperty('filesUploadEnd') && typeof parameters.filesUploadEnd == 'function')
parameters.filesUploadEnd()
// If we have field types BacalhauUrlDataset
// run Bacalhau job first and remap values with job UUID
let bacalhauJobElements = assetElements
.filter((f) => {return f.type == 'bacalhau-url-dataset'})
if (bacalhauJobElements.length)
if (parameters.hasOwnProperty('waitingBacalhauJobStart') && typeof parameters.waitingBacalhauJobStart == 'function')
parameters.waitingBacalhauJobStart()
for (const bacalhauJobElement of bacalhauJobElements) {
if(bacalhauJobElement.value == null)
continue
const type = 'url-dataset'
const parameters = ''
const inputs = bacalhauJobElement.value.inputs
const container = 'ghcr.io/bacalhau-project/examples/upload:v1'
const commands = ''
const swarm = []
let runBacalhauJobResponse
try {
runBacalhauJobResponse = await this.runBacalhauJob(type, parameters, inputs, container, commands, swarm)
} catch (error) {
if (parameters.hasOwnProperty('error') && typeof parameters.error == 'function')
parameters.error(error)
return
}
bacalhauJobElement.value = {
type: (bacalhauJobElement.value.type) ? bacalhauJobElement.value.type : null,
inputs: (bacalhauJobElement.value.inputs) ? bacalhauJobElement.value.inputs : null,
swarm: (bacalhauJobElement.value.swarm) ? bacalhauJobElement.value.swarm : null,
job_uuid: (runBacalhauJobResponse.result.job_uuid) ? runBacalhauJobResponse.result.job_uuid : null
}
}
if (bacalhauJobElements.length)
if (parameters.hasOwnProperty('bacalhauJobStarted') && typeof parameters.bacalhauJobStarted == 'function')
parameters.bacalhauJobStarted()
// If we have field types BacalhauCustomDockerJob
// run Bacalhau job first and remap values with job UUID
let bacalhauCustomDockerJobElements = assetElements
.filter((f) => {return f.type == 'bacalhau-custom-docker-job-with-url-inputs'
|| f.type == 'bacalhau-custom-docker-job-with-cid-inputs' || f.type == 'bacalhau-custom-docker-job-without-inputs'
|| f.type == 'bacalhau-wasm-job'})
if (bacalhauCustomDockerJobElements.length)
if (parameters.hasOwnProperty('waitingBacalhauJobStart') && typeof parameters.waitingBacalhauJobStart == 'function')
parameters.waitingBacalhauJobStart()
for (const bacalhauCustomDockerJobElement of bacalhauCustomDockerJobElements) {
if(bacalhauCustomDockerJobElement.value == null)
continue
const type = bacalhauCustomDockerJobElement.value.type
const parameters = bacalhauCustomDockerJobElement.value.parameters
const inputs = bacalhauCustomDockerJobElement.value.inputs
const container = bacalhauCustomDockerJobElement.value.container
const commands = bacalhauCustomDockerJobElement.value.commands
const swarm = bacalhauCustomDockerJobElement.value.swarm
let runBacalhauCustomDockerJobResponse
try {
runBacalhauCustomDockerJobResponse = await this.runBacalhauJob(type, parameters, inputs, container, commands, swarm)
} catch (error) {
if (parameters.hasOwnProperty('error') && typeof parameters.error == 'function')
parameters.error(error)
return
}
bacalhauCustomDockerJobElement.value = {
type: (bacalhauCustomDockerJobElement.value.type) ? bacalhauCustomDockerJobElement.value.type : null,
parameters: (bacalhauCustomDockerJobElement.value.parameters) ? bacalhauCustomDockerJobElement.value.parameters : null,
inputs: (bacalhauCustomDockerJobElement.value.inputs) ? bacalhauCustomDockerJobElement.value.inputs : null,
container: (bacalhauCustomDockerJobElement.value.container) ? bacalhauCustomDockerJobElement.value.container : null,
commands: (bacalhauCustomDockerJobElement.value.commands) ? bacalhauCustomDockerJobElement.value.commands : null,
swarm: (bacalhauCustomDockerJobElement.value.swarm) ? bacalhauCustomDockerJobElement.value.swarm : null,
job_uuid: (runBacalhauCustomDockerJobResponse.result.job_uuid) ? runBacalhauCustomDockerJobResponse.result.job_uuid : null
}
}
if (bacalhauCustomDockerJobElements.length)
if (parameters.hasOwnProperty('bacalhauJobStarted') && typeof parameters.bacalhauJobStarted == 'function')
parameters.bacalhauJobStarted()
// date, datetime
let dateContainingElements = assetElements
.filter((f) => {return f.type == 'date' || f.type == 'datetime'})
for (const dateContainingElement of dateContainingElements) {
if(dateContainingElement.value == null)
continue
try {
dateContainingElement.value = dateContainingElement.value.toISOString()
} catch (error) {
}
}
// dates, datetimes, daterange(s)
let datesContainingElements = assetElements
.filter((f) => {return f.type == 'dates' || f.type == 'datetimes' || f.type == 'daterange' || f.type == 'datetimerange'})
for (const datesContainingElement of datesContainingElements) {
if(datesContainingElement.value == null)
continue
try {
datesContainingElement.value = datesContainingElement.value.map((v) => {return v.toISOString()})
} catch (error) {
}
}
return {
assetElements: assetElements,
error: null
}
}
async _extractNestedTemplates(template, levelOneOnly) {
if(Array.isArray(template)) {
for (let el of template) {
if(Array.isArray(el)) {
let val = el[1]
if((val.type.toLowerCase() == 'schema' || val.type.toLowerCase() == 'template' || val.type.toLowerCase() == 'schema-list' || val.type.toLowerCase() == 'template-list') && val.value && typeof val.value != 'string') {
try {
const cid = this.makeCid(val.value)
const subTemplate = (await this.ipfs.dag.get(cid)).value
val.value = subTemplate
if(!levelOneOnly)
for (const subTemplateKey of Object.keys(subTemplate)) {
if((subTemplate[subTemplateKey].type.toLowerCase() == 'schema' || subTemplate[subTemplateKey].type.toLowerCase() == 'template' || subTemplate[subTemplateKey].type.toLowerCase() == 'schema-list' || subTemplate[subTemplateKey].type.toLowerCase() == 'template-list') && subTemplate[subTemplateKey].value)
val.value = await this._extractNestedTemplates(val.value)
}
} catch (error) {
// console.error(error)
}
}
}
else {
const key = Object.keys(el)[0]
let val = el[key]
if((val.type.toLowerCase() == 'schema' || val.type.toLowerCase() == 'template' || val.type.toLowerCase() == 'schema-list' || val.type.toLowerCase() == 'template-list') && val.value && typeof val.value != 'string') {
try {
const cid = this.makeCid(val.value)
let subTemplate = (await this.ipfs.dag.get(cid)).value
val.value = subTemplate
if(!levelOneOnly)
for (const subTemplateKey of Object.keys(subTemplate)) {
if((subTemplate[subTemplateKey].type.toLowerCase() == 'schema' || subTemplate[subTemplateKey].type.toLowerCase() == 'template' || subTemplate[subTemplateKey].type.toLowerCase() == 'schema-list' || subTemplate[subTemplateKey].type.toLowerCase() == 'template-list') && subTemplate[subTemplateKey].value)
val.value = await this._extractNestedTemplates(val.value)
}
} catch (error) {
// console.error(error)
}
}
}
}
}
else {
const keys = Object.keys(template)
for (const key of keys) {
let val = template[key]
if((val.type.toLowerCase() == 'schema' || val.type.toLowerCase() == 'template' || val.type.toLowerCase() == 'schema-list' || val.type.toLowerCase() == 'template-list') && val.value && typeof val.value != 'string') {
try {
const cid = this.makeCid(val.value)
let subTemplate = (await this.ipfs.dag.get(cid)).value
val.value = subTemplate
if(!levelOneOnly)
for (const subTemplateKey of Object.keys(subTemplate)) {
if((subTemplate[subTemplateKey].type.toLowerCase() == 'schema' || subTemplate[subTemplateKey].type.toLowerCase() == 'template' || subTemplate[subTemplateKey].type.toLowerCase() == 'schema-list' || subTemplate[subTemplateKey].type.toLowerCase() == 'template-list') && subTemplate[subTemplateKey].value)
val.value = await this._extractNestedTemplates(val.value)
}
} catch (error) {
// console.error(error)
}
}
}
}
return template
}
async getAsset(assetBlockCid) {
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let assetBlock, asset
try {
assetBlockCid = CID.parse(assetBlockCid)
assetBlock = (await this.ipfs.dag.get(assetBlockCid)).value
asset = (await this.ipfs.dag.get(CID.parse(assetBlock.cid))).value
} catch (error) {
return new Promise((resolve, reject) => {
reject({
error: error,
result: null
})
})
}
return new Promise((resolve, reject) => {
resolve({
result: {
block: assetBlockCid.toString(),
assetBlock: assetBlock,
asset: asset
},
error: null
})
})
}
async updateAccount(assets, templates, provenance, functions, pipelines, chainName) {
const that = this
try {
await this.ensureIpfsIsRunning()
}
catch(error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
if(this.fgApiToken == undefined)
try {
this.fgApiToken = (await this.getApiToken(true)).result.data.token
} catch (error) {
return new Promise((resolve, reject) => {
reject({
result: null,
error: error
})
})
}
let account
try {
account = await this.getAccount(chainName)
} catch (error) {
return new Promise((resolve,