1
1
import path from "path" ;
2
2
import type { Express } from "express" ;
3
- import { create , IPFSHTTPClient } from "ipfs-http-client" ;
4
- import type { Options } from "ipfs-http-client" ;
5
3
import { RuntimeError } from "run-time-error" ;
6
4
import { Logger , Checks , LoggerProvider } from "@hyperledger/cactus-common" ;
7
5
import type { LogLevelDesc } from "@hyperledger/cactus-common" ;
@@ -22,21 +20,25 @@ import OAS from "../json/openapi.json";
22
20
import { GetObjectEndpointV1 } from "./web-services/get-object-endpoint-v1" ;
23
21
import { SetObjectEndpointV1 } from "./web-services/set-object-endpoint-v1" ;
24
22
import { HasObjectEndpointV1 } from "./web-services/has-object-endpoint-v1" ;
25
- import { isIpfsHttpClientOptions } from "./i-ipfs-http-client" ;
23
+ import {
24
+ LikeIpfsHttpClient ,
25
+ isLikeIpfsHttpClient ,
26
+ Options ,
27
+ } from "./kubo-rpc-client-types" ;
26
28
27
29
export const K_IPFS_JS_HTTP_ERROR_FILE_DOES_NOT_EXIST =
28
30
"HTTPError: file does not exist" ;
29
31
30
32
export interface IPluginObjectStoreIpfsOptions extends ICactusPluginOptions {
31
33
readonly logLevel ?: LogLevelDesc ;
32
34
readonly parentDir : string ;
33
- readonly ipfsClientOrOptions : Options | IPFSHTTPClient ;
35
+ readonly ipfsClientOrOptions : Options | LikeIpfsHttpClient ;
34
36
}
35
37
36
38
export class PluginObjectStoreIpfs implements IPluginObjectStore {
37
39
public static readonly CLASS_NAME = "PluginObjectStoreIpfs" ;
38
40
39
- private readonly ipfs : IPFSHTTPClient ;
41
+ private ipfs : LikeIpfsHttpClient | undefined ;
40
42
private readonly log : Logger ;
41
43
private readonly instanceId : string ;
42
44
private readonly parentDir : string ;
@@ -45,25 +47,48 @@ export class PluginObjectStoreIpfs implements IPluginObjectStore {
45
47
return PluginObjectStoreIpfs . CLASS_NAME ;
46
48
}
47
49
50
+ /**
51
+ * We use dynamic import for kubo-rpc-client since it's ESM and we can't import it normally.
52
+ * This methods will load the module and initialize local IPFS client based on ctor arguments.
53
+ */
54
+ private async initIpfs ( ) : Promise < void > {
55
+ if ( isLikeIpfsHttpClient ( this . opts . ipfsClientOrOptions ) ) {
56
+ this . ipfs = this . opts . ipfsClientOrOptions ;
57
+ } else if ( this . opts . ipfsClientOrOptions ) {
58
+ // Force no transpilation (see https://github.com/microsoft/TypeScript/issues/43329)
59
+ const kuboRpcModule = await ( eval ( 'import("kubo-rpc-client")' ) as Promise <
60
+ typeof import ( "kubo-rpc-client" )
61
+ > ) ;
62
+ this . ipfs = kuboRpcModule . create ( this . opts . ipfsClientOrOptions ) ;
63
+ } else {
64
+ const errorMessage = `initIpfs Need either "ipfsClient" or "ipfsClientOptions" to construct ${ this . className } Neither was provided.` ;
65
+ throw new RuntimeError ( errorMessage ) ;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Get IPFS client or initialize it from constructor args.
71
+ * @returns `LikeIpfsHttpClient` or exception
72
+ */
73
+ private async getIpfs ( ) : Promise < LikeIpfsHttpClient > {
74
+ if ( ! this . ipfs ) {
75
+ await this . initIpfs ( ) ;
76
+ }
77
+
78
+ if ( ! this . ipfs ) {
79
+ throw new Error ( "Could not instantiate ipfs http client" ) ;
80
+ }
81
+
82
+ return this . ipfs ;
83
+ }
84
+
48
85
constructor ( public readonly opts : IPluginObjectStoreIpfsOptions ) {
49
86
const fnTag = `${ this . className } #constructor()` ;
50
87
Checks . truthy ( opts , `${ fnTag } arg options` ) ;
51
88
Checks . nonBlankString ( opts . instanceId , `${ fnTag } options.instanceId` ) ;
52
89
Checks . nonBlankString ( opts . parentDir , `${ fnTag } options.parentDir` ) ;
53
90
Checks . truthy ( opts . ipfsClientOrOptions , `${ fnTag } ipfsClientOrOptions` ) ;
54
91
55
- if ( isIpfsHttpClientOptions ( opts . ipfsClientOrOptions ) ) {
56
- this . ipfs = opts . ipfsClientOrOptions ;
57
- } else if ( opts . ipfsClientOrOptions ) {
58
- this . ipfs = create ( {
59
- ...( this . opts . ipfsClientOrOptions as Options ) ,
60
- } ) ;
61
- } else {
62
- const errorMessage = `${ fnTag } Need either "ipfsClient" or "ipfsClientOptions" to construct ${ this . className } Neither was provided.` ;
63
- throw new RuntimeError ( errorMessage ) ;
64
- }
65
- Checks . truthy ( this . ipfs , `${ fnTag } arg options.backend` ) ;
66
-
67
92
const level = this . opts . logLevel || "INFO" ;
68
93
const label = this . className ;
69
94
this . log = LoggerProvider . getOrCreate ( { level, label } ) ;
@@ -79,7 +104,7 @@ export class PluginObjectStoreIpfs implements IPluginObjectStore {
79
104
}
80
105
81
106
public async onPluginInit ( ) : Promise < unknown > {
82
- return ; // no-op
107
+ return this . initIpfs ( ) ;
83
108
}
84
109
85
110
public async registerWebServices (
@@ -130,7 +155,8 @@ export class PluginObjectStoreIpfs implements IPluginObjectStore {
130
155
131
156
public async get ( req : GetObjectRequestV1 ) : Promise < GetObjectResponseV1 > {
132
157
const keyPath = this . getKeyPath ( req ) ;
133
- const chunksIterable = this . ipfs . files . read ( keyPath ) ;
158
+ const ipfs = await this . getIpfs ( ) ;
159
+ const chunksIterable = ipfs . files . read ( keyPath ) ;
134
160
const chunks = [ ] ;
135
161
for await ( const chunk of chunksIterable ) {
136
162
chunks . push ( chunk ) ;
@@ -151,7 +177,8 @@ export class PluginObjectStoreIpfs implements IPluginObjectStore {
151
177
const checkedAt = new Date ( ) . toJSON ( ) ;
152
178
const keyPath = this . getKeyPath ( req ) ;
153
179
try {
154
- const statResult = await this . ipfs . files . stat ( keyPath ) ;
180
+ const ipfs = await this . getIpfs ( ) ;
181
+ const statResult = await ipfs . files . stat ( keyPath ) ;
155
182
this . log . debug ( `StatResult for ${ req . key } : %o` , statResult ) ;
156
183
return { key : req . key , checkedAt, isPresent : true } ;
157
184
} catch ( ex ) {
@@ -170,7 +197,8 @@ export class PluginObjectStoreIpfs implements IPluginObjectStore {
170
197
try {
171
198
this . log . debug ( `Seting object ${ keyPath } in IPFS...` ) ;
172
199
const buffer = Buffer . from ( req . value , "base64" ) ;
173
- await this . ipfs . files . write ( keyPath , buffer , {
200
+ const ipfs = await this . getIpfs ( ) ;
201
+ await ipfs . files . write ( keyPath , buffer , {
174
202
create : true ,
175
203
parents : true ,
176
204
} ) ;
0 commit comments