@lumeweb/pinner 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -0
- package/dist/esm/_virtual/{rolldown_runtime.js → _rolldown/runtime.js} +2 -2
- package/dist/esm/adapters/pinata/index.d.ts +1 -4
- package/dist/esm/adapters/pinata/legacy/adapter.d.ts +0 -1
- package/dist/esm/adapters/pinata/legacy/adapter.js +42 -1
- package/dist/esm/adapters/pinata/legacy/adapter.js.map +1 -1
- package/dist/esm/adapters/pinata/shared/index.d.ts +1 -2
- package/dist/esm/adapters/pinata/v2/adapter-interface.d.ts +0 -1
- package/dist/esm/adapters/pinata/v2/adapter.d.ts +0 -1
- package/dist/esm/adapters/pinata/v2/adapter.js +3 -3
- package/dist/esm/adapters/pinata/v2/adapter.js.map +1 -1
- package/dist/esm/api/generated/schemas/iPNSKeyListResponse.d.ts +46 -0
- package/dist/esm/api/generated/schemas/iPNSKeyListResponseResponse.d.ts +10 -0
- package/dist/esm/api/generated/schemas/iPNSKeyRequest.d.ts +43 -0
- package/dist/esm/api/generated/schemas/iPNSKeyResponse.d.ts +46 -0
- package/dist/esm/api/generated/schemas/iPNSPublishRequest.d.ts +44 -0
- package/dist/esm/api/generated/schemas/iPNSPublishResponse.d.ts +46 -0
- package/dist/esm/api/generated/schemas/iPNSResolveResponse.d.ts +47 -0
- package/dist/esm/api/generated/schemas/sSLStatusInfo.d.ts +45 -0
- package/dist/esm/api/generated/schemas/websiteConfigResponse.d.ts +43 -0
- package/dist/esm/api/generated/schemas/websiteItem.d.ts +23 -0
- package/dist/esm/api/generated/schemas/websiteItemResponse.d.ts +10 -0
- package/dist/esm/api/generated/schemas/websiteRequest.d.ts +45 -0
- package/dist/esm/api/generated/schemas/websiteResponse.d.ts +23 -0
- package/dist/esm/api/generated/schemas/websiteUpdateRequest.d.ts +45 -0
- package/dist/esm/api/generated/schemas/websiteValidateResponse.d.ts +45 -0
- package/dist/esm/api/ipns.d.ts +28 -0
- package/dist/esm/api/ipns.js +83 -0
- package/dist/esm/api/ipns.js.map +1 -0
- package/dist/esm/api/websites.d.ts +48 -0
- package/dist/esm/api/websites.js +168 -0
- package/dist/esm/api/websites.js.map +1 -0
- package/dist/esm/blockstore/unstorage-base.d.ts +4 -1
- package/dist/esm/blockstore/unstorage-base.js +3 -3
- package/dist/esm/blockstore/unstorage-base.js.map +1 -1
- package/dist/esm/blockstore/unstorage.d.ts +3 -31
- package/dist/esm/blockstore/unstorage.js +2 -2
- package/dist/esm/blockstore/unstorage.js.map +1 -1
- package/dist/esm/encoder/base64.js.map +1 -1
- package/dist/esm/encoder/csv/row-formatter.js.map +1 -1
- package/dist/esm/encoder/csv.js.map +1 -1
- package/dist/esm/encoder/json.js.map +1 -1
- package/dist/esm/encoder/text.js.map +1 -1
- package/dist/esm/encoder/url.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -1
- package/dist/esm/pin/client.js +3 -2
- package/dist/esm/pin/client.js.map +1 -1
- package/dist/esm/pinner.d.ts +12 -0
- package/dist/esm/pinner.js +18 -0
- package/dist/esm/pinner.js.map +1 -1
- package/dist/esm/types/pin.d.ts +0 -1
- package/dist/esm/types/type-guards.d.ts +0 -1
- package/dist/esm/types/upload.d.ts +0 -1
- package/dist/esm/upload/base-upload.js +3 -3
- package/dist/esm/upload/base-upload.js.map +1 -1
- package/dist/esm/upload/builder.d.ts +0 -1
- package/dist/esm/upload/car.js +3 -3
- package/dist/esm/upload/car.js.map +1 -1
- package/dist/esm/upload/manager.js +5 -5
- package/dist/esm/upload/manager.js.map +1 -1
- package/dist/esm/upload/normalize.js +1 -1
- package/dist/esm/upload/normalize.js.map +1 -1
- package/dist/esm/utils/stream.d.ts +0 -3
- package/dist/esm/utils/tus-patch.js +2 -2
- package/dist/esm/utils/tus-patch.js.map +1 -1
- package/dist/esm/utils/validation.js.map +1 -1
- package/package.json +33 -32
- package/dist/cjs/_virtual/rolldown_runtime.cjs +0 -29
- package/dist/cjs/adapters/pinata/index.cjs +0 -6
- package/dist/cjs/adapters/pinata/legacy/adapter.cjs +0 -83
- package/dist/cjs/adapters/pinata/legacy/adapter.cjs.map +0 -1
- package/dist/cjs/adapters/pinata/legacy/adapter.d.cts +0 -74
- package/dist/cjs/adapters/pinata/legacy/index.cjs +0 -1
- package/dist/cjs/adapters/pinata/shared/index.cjs +0 -1
- package/dist/cjs/adapters/pinata/shared/types.d.cts +0 -218
- package/dist/cjs/adapters/pinata/shared/utils.cjs +0 -83
- package/dist/cjs/adapters/pinata/shared/utils.cjs.map +0 -1
- package/dist/cjs/adapters/pinata/v2/adapter-interface.d.cts +0 -198
- package/dist/cjs/adapters/pinata/v2/adapter.cjs +0 -636
- package/dist/cjs/adapters/pinata/v2/adapter.cjs.map +0 -1
- package/dist/cjs/adapters/pinata/v2/adapter.d.cts +0 -17
- package/dist/cjs/adapters/pinata/v2/index.cjs +0 -1
- package/dist/cjs/adapters/pinata/v2/types.d.cts +0 -308
- package/dist/cjs/blockstore/index.cjs +0 -2
- package/dist/cjs/blockstore/unstorage-base.cjs +0 -240
- package/dist/cjs/blockstore/unstorage-base.cjs.map +0 -1
- package/dist/cjs/blockstore/unstorage-base.d.cts +0 -23
- package/dist/cjs/blockstore/unstorage.cjs +0 -39
- package/dist/cjs/blockstore/unstorage.cjs.map +0 -1
- package/dist/cjs/blockstore/unstorage.d.cts +0 -36
- package/dist/cjs/config.d.cts +0 -51
- package/dist/cjs/encoder/base64.cjs +0 -38
- package/dist/cjs/encoder/base64.cjs.map +0 -1
- package/dist/cjs/encoder/csv/csv-formatter.cjs +0 -81
- package/dist/cjs/encoder/csv/csv-formatter.cjs.map +0 -1
- package/dist/cjs/encoder/csv/field-formatter.cjs +0 -76
- package/dist/cjs/encoder/csv/field-formatter.cjs.map +0 -1
- package/dist/cjs/encoder/csv/row-formatter.cjs +0 -159
- package/dist/cjs/encoder/csv/row-formatter.cjs.map +0 -1
- package/dist/cjs/encoder/csv.cjs +0 -44
- package/dist/cjs/encoder/csv.cjs.map +0 -1
- package/dist/cjs/encoder/error.cjs +0 -19
- package/dist/cjs/encoder/error.cjs.map +0 -1
- package/dist/cjs/encoder/index.cjs +0 -6
- package/dist/cjs/encoder/json.cjs +0 -36
- package/dist/cjs/encoder/json.cjs.map +0 -1
- package/dist/cjs/encoder/text.cjs +0 -35
- package/dist/cjs/encoder/text.cjs.map +0 -1
- package/dist/cjs/encoder/url.cjs +0 -39
- package/dist/cjs/encoder/url.cjs.map +0 -1
- package/dist/cjs/errors/index.cjs +0 -104
- package/dist/cjs/errors/index.cjs.map +0 -1
- package/dist/cjs/errors/index.d.cts +0 -47
- package/dist/cjs/index.cjs +0 -44
- package/dist/cjs/index.d.cts +0 -16
- package/dist/cjs/pin/client.cjs +0 -98
- package/dist/cjs/pin/client.cjs.map +0 -1
- package/dist/cjs/pin/index.cjs +0 -1
- package/dist/cjs/pinner.cjs +0 -132
- package/dist/cjs/pinner.cjs.map +0 -1
- package/dist/cjs/pinner.d.cts +0 -81
- package/dist/cjs/types/constants.cjs +0 -39
- package/dist/cjs/types/constants.cjs.map +0 -1
- package/dist/cjs/types/mime-types.cjs +0 -11
- package/dist/cjs/types/mime-types.cjs.map +0 -1
- package/dist/cjs/types/mime-types.d.cts +0 -7
- package/dist/cjs/types/pin.d.cts +0 -78
- package/dist/cjs/types/type-guards.cjs +0 -20
- package/dist/cjs/types/type-guards.cjs.map +0 -1
- package/dist/cjs/types/type-guards.d.cts +0 -15
- package/dist/cjs/types/upload.cjs +0 -18
- package/dist/cjs/types/upload.cjs.map +0 -1
- package/dist/cjs/types/upload.d.cts +0 -189
- package/dist/cjs/upload/base-upload.cjs +0 -135
- package/dist/cjs/upload/base-upload.cjs.map +0 -1
- package/dist/cjs/upload/builder.cjs +0 -174
- package/dist/cjs/upload/builder.cjs.map +0 -1
- package/dist/cjs/upload/builder.d.cts +0 -60
- package/dist/cjs/upload/car.cjs +0 -129
- package/dist/cjs/upload/car.cjs.map +0 -1
- package/dist/cjs/upload/car.d.cts +0 -19
- package/dist/cjs/upload/constants.cjs +0 -9
- package/dist/cjs/upload/constants.cjs.map +0 -1
- package/dist/cjs/upload/index.cjs +0 -8
- package/dist/cjs/upload/manager.cjs +0 -249
- package/dist/cjs/upload/manager.cjs.map +0 -1
- package/dist/cjs/upload/manager.d.cts +0 -35
- package/dist/cjs/upload/normalize.cjs +0 -28
- package/dist/cjs/upload/normalize.cjs.map +0 -1
- package/dist/cjs/upload/tus-upload.cjs +0 -74
- package/dist/cjs/upload/tus-upload.cjs.map +0 -1
- package/dist/cjs/upload/xhr-upload.cjs +0 -41
- package/dist/cjs/upload/xhr-upload.cjs.map +0 -1
- package/dist/cjs/utils/env.cjs +0 -12
- package/dist/cjs/utils/env.cjs.map +0 -1
- package/dist/cjs/utils/stream.cjs +0 -141
- package/dist/cjs/utils/stream.cjs.map +0 -1
- package/dist/cjs/utils/stream.d.cts +0 -23
- package/dist/cjs/utils/tus-patch.cjs +0 -50
- package/dist/cjs/utils/tus-patch.cjs.map +0 -1
- package/dist/cjs/utils/validation.cjs +0 -62
- package/dist/cjs/utils/validation.cjs.map +0 -1
package/dist/esm/pin/client.js
CHANGED
|
@@ -12,11 +12,12 @@ var PinClient = class {
|
|
|
12
12
|
getClient() {
|
|
13
13
|
if (this.client) return this.client;
|
|
14
14
|
if (!this.config.jwt) throw new ConfigurationError("JWT token is required");
|
|
15
|
-
|
|
15
|
+
const configuration = new Configuration({
|
|
16
16
|
endpointUrl: this.config.endpoint,
|
|
17
17
|
accessToken: this.config.jwt,
|
|
18
18
|
fetchApi: this.config.fetch ?? fetch
|
|
19
|
-
})
|
|
19
|
+
});
|
|
20
|
+
this.client = new RemotePinningServiceClient(configuration);
|
|
20
21
|
return this.client;
|
|
21
22
|
}
|
|
22
23
|
async *add(cid, options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/pin/client.ts"],"sourcesContent":["import {\n Configuration,\n type Pin,\n type PinStatus,\n RemotePinningServiceClient,\n} from \"@ipfs-shipyard/pinning-service-client\";\nimport type { PinnerConfig } from \"../config\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\nimport { ConfigurationError, NotFoundError } from \"@/errors\";\n\nexport class PinClient implements RemotePins {\n private client: RemotePinningServiceClient | null = null;\n private config: PinnerConfig;\n\n constructor(config: PinnerConfig) {\n this.config = config;\n }\n\n protected getClient(): RemotePinningServiceClient {\n if (this.client) {\n return this.client;\n }\n\n if (!this.config.jwt) {\n throw new ConfigurationError(\"JWT token is required\");\n }\n\n const configuration = new Configuration({\n endpointUrl: this.config.endpoint,\n accessToken: this.config.jwt,\n fetchApi: this.config.fetch ?? fetch,\n });\n\n this.client = new RemotePinningServiceClient(configuration);\n return this.client;\n }\n\n async *add(\n cid: CID,\n options?: RemoteAddOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = this.getClient();\n\n const pin: Pin = {\n cid: cid.toString(),\n name: options?.name,\n meta: options?.metadata,\n origins: options?.origins,\n };\n\n await client.pinsPost({ pin }, { signal: options?.signal });\n\n yield cid;\n }\n\n async *ls(\n options?: RemoteLsOptions,\n ): AsyncGenerator<RemotePin, void, undefined> {\n const client = this.getClient();\n const response = await client.pinsGet(this.normalizeListOptions(options), {\n signal: options?.signal,\n });\n\n for (const result of response.results) {\n yield this.mapResponse(result);\n }\n }\n\n async isPinned(cid: CID, options?: AbortOptions): Promise<boolean> {\n try {\n await this.get(cid, options);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(cid: CID, options?: AbortOptions): Promise<RemotePin> {\n const client = this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n {\n signal: options?.signal,\n },\n );\n\n if (response.results.length === 0) {\n throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);\n }\n\n return this.mapResponse(response.results[0]);\n }\n\n async setMetadata(\n cid: CID,\n metadata: Record<string, string> | undefined,\n options?: AbortOptions,\n ): Promise<void> {\n const client = this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n {\n signal: options?.signal,\n },\n );\n\n if (response.results.length === 0) {\n throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);\n }\n\n const pin = response.results[0];\n await client.pinsRequestidPost(\n {\n requestid: pin.requestid,\n pin: {\n cid: pin.pin.cid,\n name: pin.pin.name,\n meta: metadata,\n origins: pin.pin.origins,\n },\n },\n { signal: options?.signal },\n );\n }\n\n async *rm(\n cid: CID,\n options?: AbortOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n { signal: options?.signal },\n );\n\n // Delete all pins for this CID by their request IDs\n await Promise.all(\n [...response.results].map(async (result) => {\n return this.rmByRequestId(result.requestid, options);\n }),\n );\n\n yield cid;\n }\n\n async rmByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n const client = this.getClient();\n await client.pinsRequestidDelete(\n { requestid: requestId },\n { signal: options?.signal },\n );\n }\n\n private mapResponse(response: PinStatus): RemotePin {\n return {\n cid: CID.parse(response.pin.cid),\n name: response.pin.name,\n status: response.status,\n created: response.created,\n size: response.pin.meta?.size\n ? parseInt(response.pin.meta.size, 10)\n : undefined,\n metadata: response.pin.meta,\n };\n }\n\n private normalizeListOptions(\n options?: RemoteLsOptions,\n ): Record<string, unknown> {\n const request: Record<string, unknown> = {};\n\n if (options?.limit !== undefined) {\n request.limit = options.limit;\n }\n if (options?.cursor !== undefined) {\n request.after = options.cursor;\n }\n if (options?.status !== undefined) {\n request.status = options.status;\n }\n if (options?.name !== undefined) {\n request.name = options.name;\n }\n\n return request;\n }\n}\n"],"mappings":";;;;;AAiBA,IAAa,YAAb,MAA6C;CAC3C,AAAQ,SAA4C;CACpD,AAAQ;CAER,YAAY,QAAsB;AAChC,OAAK,SAAS;;CAGhB,AAAU,YAAwC;AAChD,MAAI,KAAK,OACP,QAAO,KAAK;AAGd,MAAI,CAAC,KAAK,OAAO,IACf,OAAM,IAAI,mBAAmB,wBAAwB;
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/pin/client.ts"],"sourcesContent":["import {\n Configuration,\n type Pin,\n type PinStatus,\n RemotePinningServiceClient,\n} from \"@ipfs-shipyard/pinning-service-client\";\nimport type { PinnerConfig } from \"../config\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\nimport { ConfigurationError, NotFoundError } from \"@/errors\";\n\nexport class PinClient implements RemotePins {\n private client: RemotePinningServiceClient | null = null;\n private config: PinnerConfig;\n\n constructor(config: PinnerConfig) {\n this.config = config;\n }\n\n protected getClient(): RemotePinningServiceClient {\n if (this.client) {\n return this.client;\n }\n\n if (!this.config.jwt) {\n throw new ConfigurationError(\"JWT token is required\");\n }\n\n const configuration = new Configuration({\n endpointUrl: this.config.endpoint,\n accessToken: this.config.jwt,\n fetchApi: this.config.fetch ?? fetch,\n });\n\n this.client = new RemotePinningServiceClient(configuration);\n return this.client;\n }\n\n async *add(\n cid: CID,\n options?: RemoteAddOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = this.getClient();\n\n const pin: Pin = {\n cid: cid.toString(),\n name: options?.name,\n meta: options?.metadata,\n origins: options?.origins,\n };\n\n await client.pinsPost({ pin }, { signal: options?.signal });\n\n yield cid;\n }\n\n async *ls(\n options?: RemoteLsOptions,\n ): AsyncGenerator<RemotePin, void, undefined> {\n const client = this.getClient();\n const response = await client.pinsGet(this.normalizeListOptions(options), {\n signal: options?.signal,\n });\n\n for (const result of response.results) {\n yield this.mapResponse(result);\n }\n }\n\n async isPinned(cid: CID, options?: AbortOptions): Promise<boolean> {\n try {\n await this.get(cid, options);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(cid: CID, options?: AbortOptions): Promise<RemotePin> {\n const client = this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n {\n signal: options?.signal,\n },\n );\n\n if (response.results.length === 0) {\n throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);\n }\n\n return this.mapResponse(response.results[0]);\n }\n\n async setMetadata(\n cid: CID,\n metadata: Record<string, string> | undefined,\n options?: AbortOptions,\n ): Promise<void> {\n const client = this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n {\n signal: options?.signal,\n },\n );\n\n if (response.results.length === 0) {\n throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);\n }\n\n const pin = response.results[0];\n await client.pinsRequestidPost(\n {\n requestid: pin.requestid,\n pin: {\n cid: pin.pin.cid,\n name: pin.pin.name,\n meta: metadata,\n origins: pin.pin.origins,\n },\n },\n { signal: options?.signal },\n );\n }\n\n async *rm(\n cid: CID,\n options?: AbortOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n { signal: options?.signal },\n );\n\n // Delete all pins for this CID by their request IDs\n await Promise.all(\n [...response.results].map(async (result) => {\n return this.rmByRequestId(result.requestid, options);\n }),\n );\n\n yield cid;\n }\n\n async rmByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n const client = this.getClient();\n await client.pinsRequestidDelete(\n { requestid: requestId },\n { signal: options?.signal },\n );\n }\n\n private mapResponse(response: PinStatus): RemotePin {\n return {\n cid: CID.parse(response.pin.cid),\n name: response.pin.name,\n status: response.status,\n created: response.created,\n size: response.pin.meta?.size\n ? parseInt(response.pin.meta.size, 10)\n : undefined,\n metadata: response.pin.meta,\n };\n }\n\n private normalizeListOptions(\n options?: RemoteLsOptions,\n ): Record<string, unknown> {\n const request: Record<string, unknown> = {};\n\n if (options?.limit !== undefined) {\n request.limit = options.limit;\n }\n if (options?.cursor !== undefined) {\n request.after = options.cursor;\n }\n if (options?.status !== undefined) {\n request.status = options.status;\n }\n if (options?.name !== undefined) {\n request.name = options.name;\n }\n\n return request;\n }\n}\n"],"mappings":"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"}
|
package/dist/esm/pinner.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { PinnerConfig } from "./config.js";
|
|
2
|
+
import { IpnsClient } from "./api/ipns.js";
|
|
3
|
+
import { WebsitesClient } from "./api/websites.js";
|
|
2
4
|
import { UploadOperation, UploadOptions, UploadResult } from "./types/upload.js";
|
|
3
5
|
import { UploadMethodAndBuilder } from "./upload/builder.js";
|
|
4
6
|
import { AbortOptions, RemoteAddOptions, RemoteLsOptions, RemotePin, RemotePins } from "./types/pin.js";
|
|
@@ -9,12 +11,22 @@ import { CID } from "multiformats/cid";
|
|
|
9
11
|
declare class Pinner {
|
|
10
12
|
private uploadManager;
|
|
11
13
|
private _pins;
|
|
14
|
+
private _ipns;
|
|
15
|
+
private _websites;
|
|
12
16
|
private _upload?;
|
|
13
17
|
constructor(config: PinnerConfig);
|
|
14
18
|
/**
|
|
15
19
|
* Access the remote pins interface.
|
|
16
20
|
*/
|
|
17
21
|
get pins(): RemotePins;
|
|
22
|
+
/**
|
|
23
|
+
* Access the IPNS interface for key management and publishing.
|
|
24
|
+
*/
|
|
25
|
+
get ipns(): IpnsClient;
|
|
26
|
+
/**
|
|
27
|
+
* Access the websites interface for website configuration and management.
|
|
28
|
+
*/
|
|
29
|
+
get websites(): WebsitesClient;
|
|
18
30
|
/**
|
|
19
31
|
* Upload interface that works as both a method and a builder namespace.
|
|
20
32
|
*
|
package/dist/esm/pinner.js
CHANGED
|
@@ -3,16 +3,22 @@ import { createUploadBuilderNamespace } from "./upload/builder.js";
|
|
|
3
3
|
import "./upload/index.js";
|
|
4
4
|
import { PinClient } from "./pin/client.js";
|
|
5
5
|
import "./pin/index.js";
|
|
6
|
+
import { IpnsClient } from "./api/ipns.js";
|
|
7
|
+
import { WebsitesClient } from "./api/websites.js";
|
|
6
8
|
import { CID } from "multiformats/cid";
|
|
7
9
|
|
|
8
10
|
//#region src/pinner.ts
|
|
9
11
|
var Pinner = class {
|
|
10
12
|
uploadManager;
|
|
11
13
|
_pins;
|
|
14
|
+
_ipns;
|
|
15
|
+
_websites;
|
|
12
16
|
_upload;
|
|
13
17
|
constructor(config) {
|
|
14
18
|
this.uploadManager = new UploadManager(config);
|
|
15
19
|
this._pins = new PinClient(config);
|
|
20
|
+
this._ipns = new IpnsClient(config);
|
|
21
|
+
this._websites = new WebsitesClient(config);
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
18
24
|
* Access the remote pins interface.
|
|
@@ -21,6 +27,18 @@ var Pinner = class {
|
|
|
21
27
|
return this._pins;
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
30
|
+
* Access the IPNS interface for key management and publishing.
|
|
31
|
+
*/
|
|
32
|
+
get ipns() {
|
|
33
|
+
return this._ipns;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Access the websites interface for website configuration and management.
|
|
37
|
+
*/
|
|
38
|
+
get websites() {
|
|
39
|
+
return this._websites;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
24
42
|
* Upload interface that works as both a method and a builder namespace.
|
|
25
43
|
*
|
|
26
44
|
* As a method: upload(file, options) -> UploadOperation
|
package/dist/esm/pinner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinner.js","names":[],"sources":["../../src/pinner.ts"],"sourcesContent":["import type { PinnerConfig } from \"./config\";\nimport { UploadManager } from \"./upload\";\nimport { PinClient } from \"./pin\";\nimport type { UploadMethodAndBuilder } from \"@/upload/builder\";\nimport { createUploadBuilderNamespace } from \"@/upload/builder\";\nimport type {\n UploadOperation,\n UploadOptions,\n UploadResult,\n} from \"@/types/upload\";\nimport type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\n\nexport class Pinner {\n private uploadManager: UploadManager;\n private _pins: RemotePins;\n private _upload?: UploadMethodAndBuilder;\n\n constructor(config: PinnerConfig) {\n this.uploadManager = new UploadManager(config);\n this._pins = new PinClient(config);\n }\n\n /**\n * Access the remote pins interface.\n */\n get pins(): RemotePins {\n return this._pins;\n }\n\n /**\n * Upload interface that works as both a method and a builder namespace.\n *\n * As a method: upload(file, options) -> UploadOperation\n * As a property: upload.file(), upload.json(), etc. -> Builder\n */\n get upload(): UploadMethodAndBuilder {\n if (!this._upload) {\n const builderNamespace = createUploadBuilderNamespace(this);\n const uploadMethod = async (\n file: File,\n options?: UploadOptions,\n ): Promise<UploadOperation> => {\n return this.uploadManager.upload(file, options);\n };\n\n this._upload = new Proxy(uploadMethod, {\n get(target, prop) {\n if (prop in builderNamespace) {\n return Reflect.get(builderNamespace, prop);\n }\n return Reflect.get(target, prop);\n },\n }) as UploadMethodAndBuilder;\n }\n return this._upload;\n }\n\n /**\n * Upload a file and wait for completion.\n * Convenience method for simple use cases where controls aren't needed.\n */\n async uploadAndWait(\n file: File,\n options?: UploadOptions,\n ): Promise<UploadResult> {\n const operation = await this.upload(file, options);\n return operation.result;\n }\n\n /**\n * Wait for an operation to complete or reach a settled state.\n * @param input Either an operation ID (number) or an UploadResult\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async waitForOperation(\n input: number | UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n return this.uploadManager.waitForOperation(input, options);\n }\n\n /**\n * Upload a directory to IPFS.\n */\n async uploadDirectory(\n files: File[],\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadDirectory(files, options);\n }\n\n /**\n * Upload a CAR file without preprocessing.\n * This is useful for passthrough of pre-generated CAR files.\n */\n async uploadCar(\n file: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadCar(file, options);\n }\n\n /**\n * Pin existing content by CID.\n */\n async pinByHash(\n cid: string | CID,\n options?: RemoteAddOptions,\n ): Promise<AsyncGenerator<CID, void, undefined>> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.add(cidObj, options);\n }\n\n /**\n * List pinned content.\n */\n async listPins(options?: RemoteLsOptions): Promise<RemotePin[]> {\n const pins: RemotePin[] = [];\n for await (const pin of this.pins.ls(options)) {\n pins.push(pin);\n }\n return pins;\n }\n\n /**\n * Get pin status.\n */\n async getPinStatus(cid: string | CID): Promise<RemotePin> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.get(cidObj);\n }\n\n /**\n * Check if content is pinned.\n */\n async isPinned(cid: string | CID): Promise<boolean> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.isPinned(cidObj);\n }\n\n /**\n * Update pin metadata.\n */\n async setPinMetadata(\n cid: string | CID,\n metadata: Record<string, string> | undefined,\n ): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.setMetadata(cidObj, metadata);\n }\n\n /**\n * Remove a pin. The block may be deleted when garbage collection is run.\n */\n async unpin(cid: string | CID, options?: AbortOptions): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n const generator = this.pins.rm(cidObj, options);\n for await (const _ of generator) {\n // Consume the generator to complete the unpin operation\n }\n }\n\n /**\n * Remove a pin by request ID. The block may be deleted when garbage collection is run.\n */\n async unpinByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n return this.pins.rmByRequestId(requestId, options);\n }\n\n /**\n * Destroy the client and cleanup resources.\n */\n destroy(): void {\n this.uploadManager.destroy();\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"pinner.js","names":[],"sources":["../../src/pinner.ts"],"sourcesContent":["import type { PinnerConfig } from \"./config\";\nimport { UploadManager } from \"./upload\";\nimport { PinClient } from \"./pin\";\nimport { IpnsClient } from \"./api/ipns\";\nimport { WebsitesClient } from \"./api/websites\";\nimport type { UploadMethodAndBuilder } from \"@/upload/builder\";\nimport { createUploadBuilderNamespace } from \"@/upload/builder\";\nimport type {\n UploadOperation,\n UploadOptions,\n UploadResult,\n} from \"@/types/upload\";\nimport type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\n\nexport class Pinner {\n private uploadManager: UploadManager;\n private _pins: RemotePins;\n private _ipns: IpnsClient;\n private _websites: WebsitesClient;\n private _upload?: UploadMethodAndBuilder;\n\n constructor(config: PinnerConfig) {\n this.uploadManager = new UploadManager(config);\n this._pins = new PinClient(config);\n this._ipns = new IpnsClient(config);\n this._websites = new WebsitesClient(config);\n }\n\n /**\n * Access the remote pins interface.\n */\n get pins(): RemotePins {\n return this._pins;\n }\n\n /**\n * Access the IPNS interface for key management and publishing.\n */\n get ipns(): IpnsClient {\n return this._ipns;\n }\n\n /**\n * Access the websites interface for website configuration and management.\n */\n get websites(): WebsitesClient {\n return this._websites;\n }\n\n /**\n * Upload interface that works as both a method and a builder namespace.\n *\n * As a method: upload(file, options) -> UploadOperation\n * As a property: upload.file(), upload.json(), etc. -> Builder\n */\n get upload(): UploadMethodAndBuilder {\n if (!this._upload) {\n const builderNamespace = createUploadBuilderNamespace(this);\n const uploadMethod = async (\n file: File,\n options?: UploadOptions,\n ): Promise<UploadOperation> => {\n return this.uploadManager.upload(file, options);\n };\n\n this._upload = new Proxy(uploadMethod, {\n get(target, prop) {\n if (prop in builderNamespace) {\n return Reflect.get(builderNamespace, prop);\n }\n return Reflect.get(target, prop);\n },\n }) as UploadMethodAndBuilder;\n }\n return this._upload;\n }\n\n /**\n * Upload a file and wait for completion.\n * Convenience method for simple use cases where controls aren't needed.\n */\n async uploadAndWait(\n file: File,\n options?: UploadOptions,\n ): Promise<UploadResult> {\n const operation = await this.upload(file, options);\n return operation.result;\n }\n\n /**\n * Wait for an operation to complete or reach a settled state.\n * @param input Either an operation ID (number) or an UploadResult\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async waitForOperation(\n input: number | UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n return this.uploadManager.waitForOperation(input, options);\n }\n\n /**\n * Upload a directory to IPFS.\n */\n async uploadDirectory(\n files: File[],\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadDirectory(files, options);\n }\n\n /**\n * Upload a CAR file without preprocessing.\n * This is useful for passthrough of pre-generated CAR files.\n */\n async uploadCar(\n file: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadCar(file, options);\n }\n\n /**\n * Pin existing content by CID.\n */\n async pinByHash(\n cid: string | CID,\n options?: RemoteAddOptions,\n ): Promise<AsyncGenerator<CID, void, undefined>> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.add(cidObj, options);\n }\n\n /**\n * List pinned content.\n */\n async listPins(options?: RemoteLsOptions): Promise<RemotePin[]> {\n const pins: RemotePin[] = [];\n for await (const pin of this.pins.ls(options)) {\n pins.push(pin);\n }\n return pins;\n }\n\n /**\n * Get pin status.\n */\n async getPinStatus(cid: string | CID): Promise<RemotePin> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.get(cidObj);\n }\n\n /**\n * Check if content is pinned.\n */\n async isPinned(cid: string | CID): Promise<boolean> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.isPinned(cidObj);\n }\n\n /**\n * Update pin metadata.\n */\n async setPinMetadata(\n cid: string | CID,\n metadata: Record<string, string> | undefined,\n ): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.setMetadata(cidObj, metadata);\n }\n\n /**\n * Remove a pin. The block may be deleted when garbage collection is run.\n */\n async unpin(cid: string | CID, options?: AbortOptions): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n const generator = this.pins.rm(cidObj, options);\n for await (const _ of generator) {\n // Consume the generator to complete the unpin operation\n }\n }\n\n /**\n * Remove a pin by request ID. The block may be deleted when garbage collection is run.\n */\n async unpinByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n return this.pins.rmByRequestId(requestId, options);\n }\n\n /**\n * Destroy the client and cleanup resources.\n */\n destroy(): void {\n this.uploadManager.destroy();\n }\n}\n"],"mappings":";;;;;;;;;;AAsBA,IAAa,SAAb,MAAoB;CAClB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAAsB;AAChC,OAAK,gBAAgB,IAAI,cAAc,OAAO;AAC9C,OAAK,QAAQ,IAAI,UAAU,OAAO;AAClC,OAAK,QAAQ,IAAI,WAAW,OAAO;AACnC,OAAK,YAAY,IAAI,eAAe,OAAO;;;;;CAM7C,IAAI,OAAmB;AACrB,SAAO,KAAK;;;;;CAMd,IAAI,OAAmB;AACrB,SAAO,KAAK;;;;;CAMd,IAAI,WAA2B;AAC7B,SAAO,KAAK;;;;;;;;CASd,IAAI,SAAiC;AACnC,MAAI,CAAC,KAAK,SAAS;GACjB,MAAM,mBAAmB,6BAA6B,KAAK;GAC3D,MAAM,eAAe,OACnB,MACA,YAC6B;AAC7B,WAAO,KAAK,cAAc,OAAO,MAAM,QAAQ;;AAGjD,QAAK,UAAU,IAAI,MAAM,cAAc,EACrC,IAAI,QAAQ,MAAM;AAChB,QAAI,QAAQ,iBACV,QAAO,QAAQ,IAAI,kBAAkB,KAAK;AAE5C,WAAO,QAAQ,IAAI,QAAQ,KAAK;MAEnC,CAAC;;AAEJ,SAAO,KAAK;;;;;;CAOd,MAAM,cACJ,MACA,SACuB;AAEvB,UAAO,MADiB,KAAK,OAAO,MAAM,QAAQ,EACjC;;;;;;;;CASnB,MAAM,iBACJ,OACA,SACuB;AACvB,SAAO,KAAK,cAAc,iBAAiB,OAAO,QAAQ;;;;;CAM5D,MAAM,gBACJ,OACA,SAC0B;AAC1B,SAAO,KAAK,cAAc,gBAAgB,OAAO,QAAQ;;;;;;CAO3D,MAAM,UACJ,MACA,SAC0B;AAC1B,SAAO,KAAK,cAAc,UAAU,MAAM,QAAQ;;;;;CAMpD,MAAM,UACJ,KACA,SAC+C;EAC/C,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,IAAI,QAAQ,QAAQ;;;;;CAMvC,MAAM,SAAS,SAAiD;EAC9D,MAAM,OAAoB,EAAE;AAC5B,aAAW,MAAM,OAAO,KAAK,KAAK,GAAG,QAAQ,CAC3C,MAAK,KAAK,IAAI;AAEhB,SAAO;;;;;CAMT,MAAM,aAAa,KAAuC;EACxD,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,IAAI,OAAO;;;;;CAM9B,MAAM,SAAS,KAAqC;EAClD,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,SAAS,OAAO;;;;;CAMnC,MAAM,eACJ,KACA,UACe;EACf,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,YAAY,QAAQ,SAAS;;;;;CAMhD,MAAM,MAAM,KAAmB,SAAuC;EACpE,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;EAC1D,MAAM,YAAY,KAAK,KAAK,GAAG,QAAQ,QAAQ;AAC/C,aAAW,MAAM,KAAK;;;;;CAQxB,MAAM,iBAAiB,WAAmB,SAAuC;AAC/E,SAAO,KAAK,KAAK,cAAc,WAAW,QAAQ;;;;;CAMpD,UAAgB;AACd,OAAK,cAAc,SAAS"}
|
package/dist/esm/types/pin.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ var BaseUploadHandler = class {
|
|
|
78
78
|
return this.#normalizeDataForBrowser(data);
|
|
79
79
|
}
|
|
80
80
|
async #normalizeDataForBrowser(data) {
|
|
81
|
-
if (this.getUploadSource() ===
|
|
81
|
+
if (this.getUploadSource() === "tus-upload") {
|
|
82
82
|
if (data instanceof ReadableStream) {
|
|
83
83
|
const { streamToBlobViaResponse } = await import("../utils/stream.js");
|
|
84
84
|
return streamToBlobViaResponse(data);
|
|
@@ -88,7 +88,7 @@ var BaseUploadHandler = class {
|
|
|
88
88
|
return data;
|
|
89
89
|
}
|
|
90
90
|
async #normalizeDataForNode(data) {
|
|
91
|
-
if (this.getUploadSource() ===
|
|
91
|
+
if (this.getUploadSource() === "xhr-upload") return data;
|
|
92
92
|
if (data instanceof File) return await readableStreamToNodeStream(fileToReadableStream(data));
|
|
93
93
|
if (data instanceof ReadableStream) return await readableStreamToNodeStream(data);
|
|
94
94
|
return data;
|
|
@@ -102,7 +102,7 @@ var BaseUploadHandler = class {
|
|
|
102
102
|
data: fileData
|
|
103
103
|
};
|
|
104
104
|
const fileId = uppy.addFile(fileOptions);
|
|
105
|
-
if (this.getUploadSource() ===
|
|
105
|
+
if (this.getUploadSource() === "tus-upload" && size !== void 0 && size > 0) uppy.setFileState(fileId, { tus: { uploadSize: size } });
|
|
106
106
|
}
|
|
107
107
|
#startUpload(uppy, options) {
|
|
108
108
|
uppy.upload().catch((error) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-upload.js","names":["#setupUppyHandlers","#addFileToUppy","#startUpload","#createUploadOperation","#extractErrorMessage","#normalizeData","#normalizeDataForNode","#normalizeDataForBrowser"],"sources":["../../../src/upload/base-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport { default as defer } from \"p-defer\";\nimport type { Readable } from \"stream\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadProgress,\n UploadResult,\n} from \"@/types/upload\";\nimport { normalizeUploadInput, type UploadInputObject } from \"./normalize\";\nimport {\n fileToReadableStream,\n readableStreamToNodeStream,\n} from \"@/utils/stream\";\nimport { isNodeEnvironment } from \"@/utils/env\";\nimport { UPLOAD_SOURCE_TUS, UPLOAD_SOURCE_XHR } from \"./constants\";\n\n// Node.js Readable stream with size property for Uppy compatibility\ntype NodeStreamWithSize = Readable & { size: number | null };\n\nexport abstract class BaseUploadHandler {\n protected config: Required<PinnerConfig>;\n\n constructor(config: PinnerConfig) {\n this.config = config as Required<PinnerConfig>;\n }\n\n async upload(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const normalized = normalizeUploadInput(input, options);\n const uppy = new Uppy();\n const { fileId, resultPromise, progress } = this.#setupUppyHandlers(\n uppy,\n normalized,\n options,\n );\n\n await this.#addFileToUppy(uppy, normalized, normalized.size);\n this.#startUpload(uppy, options);\n\n return this.#createUploadOperation(uppy, fileId, resultPromise, progress);\n }\n\n #setupUppyHandlers(\n uppy: Uppy,\n normalized: { size: number },\n options?: UploadOptions,\n ): {\n fileId: string | null;\n resultPromise: Promise<UploadResult>;\n progress: UploadProgress;\n } {\n let fileId: string | null = null;\n let hasRejected = false;\n\n const progress: UploadProgress = {\n percentage: 0,\n bytesUploaded: 0,\n bytesTotal: normalized.size,\n };\n\n const {\n promise: resultPromise,\n resolve: resolveResult,\n reject: rejectResult,\n } = defer<UploadResult>();\n\n const handleError = (error: Error) => {\n if (hasRejected) return;\n hasRejected = true;\n options?.onError?.(error);\n rejectResult(error);\n };\n\n this.configurePlugin(uppy);\n\n uppy.on(\"progress\", (progressBytes) => {\n progress.bytesUploaded = progressBytes;\n progress.percentage = (progressBytes / progress.bytesTotal) * 100;\n options?.onProgress?.(progress);\n });\n\n uppy.on(\"upload-success\", (_file, result) => {\n if (hasRejected) return;\n const uploadResult = this.parseResult(result);\n options?.onComplete?.(uploadResult);\n resolveResult(uploadResult);\n });\n\n uppy.on(\"error\", (error) => {\n handleError(new Error(this.#extractErrorMessage(error)));\n });\n\n uppy.on(\"file-added\", (file) => {\n fileId = file.id;\n });\n\n return { fileId, resultPromise, progress };\n }\n\n #extractErrorMessage(error: unknown): string {\n let errorMessage = \"Upload fainormalizeDataled\";\n\n if (!error) return errorMessage;\n\n if (typeof error === \"string\") {\n errorMessage = error;\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else if ((error as any).message) {\n errorMessage = (error as any).message;\n } else if (\n (error as any).toString &&\n typeof (error as any).toString === \"function\"\n ) {\n errorMessage = (error as any).toString();\n }\n\n // Try to extract error from response (XHRUpload/TusPlugin)\n const errorObj = error as any;\n if (errorObj?.xhr?.response) {\n try {\n const response =\n typeof errorObj.xhr.response === \"string\"\n ? JSON.parse(errorObj.xhr.response)\n : errorObj.xhr.response;\n if (response.error) {\n errorMessage = response.error;\n } else if (response.message) {\n errorMessage = response.message;\n }\n } catch {\n // If parsing fails, use the original error message\n }\n }\n\n return errorMessage;\n }\n\n async #normalizeData(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | ReadableStream<Uint8Array> | NodeStreamWithSize | Blob> {\n if (isNodeEnvironment()) {\n return this.#normalizeDataForNode(data);\n }\n return this.#normalizeDataForBrowser(data);\n }\n\n async #normalizeDataForBrowser(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | Blob | ReadableStream<Uint8Array>> {\n // TUS plugin requires File, Blob, or Reader in browser\n if (this.getUploadSource() === UPLOAD_SOURCE_TUS) {\n if (data instanceof ReadableStream) {\n const { streamToBlobViaResponse } = await import(\"@/utils/stream\");\n return streamToBlobViaResponse(data);\n }\n return data;\n }\n\n // XHRUpload handles File/Blob directly\n return data;\n }\n\n async #normalizeDataForNode(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<NodeStreamWithSize | File | Blob | ReadableStream<Uint8Array>> {\n // XHRUpload with formData: true requires Blob/File for FormData.append()\n // Do not convert to Node.js stream for XHRUpload\n if (this.getUploadSource() === UPLOAD_SOURCE_XHR) {\n return data;\n }\n\n // Convert File to ReadableStream without loading entire blob into memory\n if (data instanceof File) {\n const stream = fileToReadableStream(data);\n // Convert to Node.js stream for tus-js-client\n const nodeStream = await readableStreamToNodeStream(stream);\n return nodeStream as NodeStreamWithSize;\n }\n\n // In Node.js, convert ReadableStream to Node.js stream.Readable for tus-js-client's NodeFileReader\n if (data instanceof ReadableStream) {\n const nodeStream = await readableStreamToNodeStream(data);\n // Add size property to satisfy Uppy's type requirements\n return nodeStream as NodeStreamWithSize;\n }\n\n return data;\n }\n\n async #addFileToUppy(\n uppy: Uppy,\n normalized: {\n data: File | ReadableStream<Uint8Array>;\n name: string;\n type: string;\n },\n size?: number,\n ): Promise<void> {\n const fileData = await this.#normalizeData(normalized.data);\n const fileOptions = {\n source: this.getUploadSource(),\n name: normalized.name,\n type: normalized.type,\n data: fileData as any,\n };\n\n // Add file to Uppy first\n // Note: Uppy accepts any data type as it defers to the drivers\n const fileId = uppy.addFile(fileOptions);\n\n // Set TUS upload size if provided\n // In Node.js, streams need explicit size for tus-js-client\n // In browser, Uppy's TUS plugin may not derive size from Blob automatically\n if (\n this.getUploadSource() === UPLOAD_SOURCE_TUS &&\n size !== undefined &&\n size > 0\n ) {\n uppy.setFileState(fileId, {\n tus: { uploadSize: size },\n });\n }\n }\n\n #startUpload(uppy: Uppy, options?: UploadOptions): void {\n uppy.upload().catch((error) => {\n options?.onError?.(new Error(this.#extractErrorMessage(error)));\n });\n }\n\n #createUploadOperation(\n uppy: Uppy,\n fileId: string | null,\n resultPromise: Promise<UploadResult>,\n progress: UploadProgress,\n ): UploadOperation {\n return {\n cancel: () => {\n uppy.cancelAll();\n },\n pause: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n resume: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n result: resultPromise,\n progress: Object.freeze({ ...progress }),\n };\n }\n\n destroy(): void {\n // No-op since each upload creates its own Uppy instance\n }\n\n protected abstract configurePlugin(uppy: Uppy): void;\n protected abstract parseResult(result: unknown): UploadResult;\n protected abstract getUploadSource(): string;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-upload.js","names":["#setupUppyHandlers","#addFileToUppy","#startUpload","#createUploadOperation","#extractErrorMessage","#normalizeData","#normalizeDataForNode","#normalizeDataForBrowser"],"sources":["../../../src/upload/base-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport { default as defer } from \"p-defer\";\nimport type { Readable } from \"stream\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadProgress,\n UploadResult,\n} from \"@/types/upload\";\nimport { normalizeUploadInput, type UploadInputObject } from \"./normalize\";\nimport {\n fileToReadableStream,\n readableStreamToNodeStream,\n} from \"@/utils/stream\";\nimport { isNodeEnvironment } from \"@/utils/env\";\nimport { UPLOAD_SOURCE_TUS, UPLOAD_SOURCE_XHR } from \"./constants\";\n\n// Node.js Readable stream with size property for Uppy compatibility\ntype NodeStreamWithSize = Readable & { size: number | null };\n\nexport abstract class BaseUploadHandler {\n protected config: Required<PinnerConfig>;\n\n constructor(config: PinnerConfig) {\n this.config = config as Required<PinnerConfig>;\n }\n\n async upload(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const normalized = normalizeUploadInput(input, options);\n const uppy = new Uppy();\n const { fileId, resultPromise, progress } = this.#setupUppyHandlers(\n uppy,\n normalized,\n options,\n );\n\n await this.#addFileToUppy(uppy, normalized, normalized.size);\n this.#startUpload(uppy, options);\n\n return this.#createUploadOperation(uppy, fileId, resultPromise, progress);\n }\n\n #setupUppyHandlers(\n uppy: Uppy,\n normalized: { size: number },\n options?: UploadOptions,\n ): {\n fileId: string | null;\n resultPromise: Promise<UploadResult>;\n progress: UploadProgress;\n } {\n let fileId: string | null = null;\n let hasRejected = false;\n\n const progress: UploadProgress = {\n percentage: 0,\n bytesUploaded: 0,\n bytesTotal: normalized.size,\n };\n\n const {\n promise: resultPromise,\n resolve: resolveResult,\n reject: rejectResult,\n } = defer<UploadResult>();\n\n const handleError = (error: Error) => {\n if (hasRejected) return;\n hasRejected = true;\n options?.onError?.(error);\n rejectResult(error);\n };\n\n this.configurePlugin(uppy);\n\n uppy.on(\"progress\", (progressBytes) => {\n progress.bytesUploaded = progressBytes;\n progress.percentage = (progressBytes / progress.bytesTotal) * 100;\n options?.onProgress?.(progress);\n });\n\n uppy.on(\"upload-success\", (_file, result) => {\n if (hasRejected) return;\n const uploadResult = this.parseResult(result);\n options?.onComplete?.(uploadResult);\n resolveResult(uploadResult);\n });\n\n uppy.on(\"error\", (error) => {\n handleError(new Error(this.#extractErrorMessage(error)));\n });\n\n uppy.on(\"file-added\", (file) => {\n fileId = file.id;\n });\n\n return { fileId, resultPromise, progress };\n }\n\n #extractErrorMessage(error: unknown): string {\n let errorMessage = \"Upload fainormalizeDataled\";\n\n if (!error) return errorMessage;\n\n if (typeof error === \"string\") {\n errorMessage = error;\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else if ((error as any).message) {\n errorMessage = (error as any).message;\n } else if (\n (error as any).toString &&\n typeof (error as any).toString === \"function\"\n ) {\n errorMessage = (error as any).toString();\n }\n\n // Try to extract error from response (XHRUpload/TusPlugin)\n const errorObj = error as any;\n if (errorObj?.xhr?.response) {\n try {\n const response =\n typeof errorObj.xhr.response === \"string\"\n ? JSON.parse(errorObj.xhr.response)\n : errorObj.xhr.response;\n if (response.error) {\n errorMessage = response.error;\n } else if (response.message) {\n errorMessage = response.message;\n }\n } catch {\n // If parsing fails, use the original error message\n }\n }\n\n return errorMessage;\n }\n\n async #normalizeData(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | ReadableStream<Uint8Array> | NodeStreamWithSize | Blob> {\n if (isNodeEnvironment()) {\n return this.#normalizeDataForNode(data);\n }\n return this.#normalizeDataForBrowser(data);\n }\n\n async #normalizeDataForBrowser(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | Blob | ReadableStream<Uint8Array>> {\n // TUS plugin requires File, Blob, or Reader in browser\n if (this.getUploadSource() === UPLOAD_SOURCE_TUS) {\n if (data instanceof ReadableStream) {\n const { streamToBlobViaResponse } = await import(\"@/utils/stream\");\n return streamToBlobViaResponse(data);\n }\n return data;\n }\n\n // XHRUpload handles File/Blob directly\n return data;\n }\n\n async #normalizeDataForNode(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<NodeStreamWithSize | File | Blob | ReadableStream<Uint8Array>> {\n // XHRUpload with formData: true requires Blob/File for FormData.append()\n // Do not convert to Node.js stream for XHRUpload\n if (this.getUploadSource() === UPLOAD_SOURCE_XHR) {\n return data;\n }\n\n // Convert File to ReadableStream without loading entire blob into memory\n if (data instanceof File) {\n const stream = fileToReadableStream(data);\n // Convert to Node.js stream for tus-js-client\n const nodeStream = await readableStreamToNodeStream(stream);\n return nodeStream as NodeStreamWithSize;\n }\n\n // In Node.js, convert ReadableStream to Node.js stream.Readable for tus-js-client's NodeFileReader\n if (data instanceof ReadableStream) {\n const nodeStream = await readableStreamToNodeStream(data);\n // Add size property to satisfy Uppy's type requirements\n return nodeStream as NodeStreamWithSize;\n }\n\n return data;\n }\n\n async #addFileToUppy(\n uppy: Uppy,\n normalized: {\n data: File | ReadableStream<Uint8Array>;\n name: string;\n type: string;\n },\n size?: number,\n ): Promise<void> {\n const fileData = await this.#normalizeData(normalized.data);\n const fileOptions = {\n source: this.getUploadSource(),\n name: normalized.name,\n type: normalized.type,\n data: fileData as any,\n };\n\n // Add file to Uppy first\n // Note: Uppy accepts any data type as it defers to the drivers\n const fileId = uppy.addFile(fileOptions);\n\n // Set TUS upload size if provided\n // In Node.js, streams need explicit size for tus-js-client\n // In browser, Uppy's TUS plugin may not derive size from Blob automatically\n if (\n this.getUploadSource() === UPLOAD_SOURCE_TUS &&\n size !== undefined &&\n size > 0\n ) {\n uppy.setFileState(fileId, {\n tus: { uploadSize: size },\n });\n }\n }\n\n #startUpload(uppy: Uppy, options?: UploadOptions): void {\n uppy.upload().catch((error) => {\n options?.onError?.(new Error(this.#extractErrorMessage(error)));\n });\n }\n\n #createUploadOperation(\n uppy: Uppy,\n fileId: string | null,\n resultPromise: Promise<UploadResult>,\n progress: UploadProgress,\n ): UploadOperation {\n return {\n cancel: () => {\n uppy.cancelAll();\n },\n pause: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n resume: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n result: resultPromise,\n progress: Object.freeze({ ...progress }),\n };\n }\n\n destroy(): void {\n // No-op since each upload creates its own Uppy instance\n }\n\n protected abstract configurePlugin(uppy: Uppy): void;\n protected abstract parseResult(result: unknown): UploadResult;\n protected abstract getUploadSource(): string;\n}\n"],"mappings":";;;;;;;;AAuBA,IAAsB,oBAAtB,MAAwC;CACtC,AAAU;CAEV,YAAY,QAAsB;AAChC,OAAK,SAAS;;CAGhB,MAAM,OACJ,OACA,SAC0B;EAC1B,MAAM,aAAa,qBAAqB,OAAO,QAAQ;EACvD,MAAM,OAAO,IAAI,MAAM;EACvB,MAAM,EAAE,QAAQ,eAAe,aAAa,MAAKA,kBAC/C,MACA,YACA,QACD;AAED,QAAM,MAAKC,cAAe,MAAM,YAAY,WAAW,KAAK;AAC5D,QAAKC,YAAa,MAAM,QAAQ;AAEhC,SAAO,MAAKC,sBAAuB,MAAM,QAAQ,eAAe,SAAS;;CAG3E,mBACE,MACA,YACA,SAKA;EACA,IAAI,SAAwB;EAC5B,IAAI,cAAc;EAElB,MAAM,WAA2B;GAC/B,YAAY;GACZ,eAAe;GACf,YAAY,WAAW;GACxB;EAED,MAAM,EACJ,SAAS,eACT,SAAS,eACT,QAAQ,iBACN,OAAqB;EAEzB,MAAM,eAAe,UAAiB;AACpC,OAAI,YAAa;AACjB,iBAAc;AACd,YAAS,UAAU,MAAM;AACzB,gBAAa,MAAM;;AAGrB,OAAK,gBAAgB,KAAK;AAE1B,OAAK,GAAG,aAAa,kBAAkB;AACrC,YAAS,gBAAgB;AACzB,YAAS,aAAc,gBAAgB,SAAS,aAAc;AAC9D,YAAS,aAAa,SAAS;IAC/B;AAEF,OAAK,GAAG,mBAAmB,OAAO,WAAW;AAC3C,OAAI,YAAa;GACjB,MAAM,eAAe,KAAK,YAAY,OAAO;AAC7C,YAAS,aAAa,aAAa;AACnC,iBAAc,aAAa;IAC3B;AAEF,OAAK,GAAG,UAAU,UAAU;AAC1B,eAAY,IAAI,MAAM,MAAKC,oBAAqB,MAAM,CAAC,CAAC;IACxD;AAEF,OAAK,GAAG,eAAe,SAAS;AAC9B,YAAS,KAAK;IACd;AAEF,SAAO;GAAE;GAAQ;GAAe;GAAU;;CAG5C,qBAAqB,OAAwB;EAC3C,IAAI,eAAe;AAEnB,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,OAAO,UAAU,SACnB,gBAAe;WACN,iBAAiB,MAC1B,gBAAe,MAAM;WACX,MAAc,QACxB,gBAAgB,MAAc;WAE7B,MAAc,YACf,OAAQ,MAAc,aAAa,WAEnC,gBAAgB,MAAc,UAAU;EAI1C,MAAM,WAAW;AACjB,MAAI,UAAU,KAAK,SACjB,KAAI;GACF,MAAM,WACJ,OAAO,SAAS,IAAI,aAAa,WAC7B,KAAK,MAAM,SAAS,IAAI,SAAS,GACjC,SAAS,IAAI;AACnB,OAAI,SAAS,MACX,gBAAe,SAAS;YACf,SAAS,QAClB,gBAAe,SAAS;UAEpB;AAKV,SAAO;;CAGT,OAAMC,cACJ,MACwE;AACxE,MAAI,mBAAmB,CACrB,QAAO,MAAKC,qBAAsB,KAAK;AAEzC,SAAO,MAAKC,wBAAyB,KAAK;;CAG5C,OAAMA,wBACJ,MACmD;AAEnD,MAAI,KAAK,iBAAiB,mBAAwB;AAChD,OAAI,gBAAgB,gBAAgB;IAClC,MAAM,EAAE,4BAA4B,MAAM,OAAO;AACjD,WAAO,wBAAwB,KAAK;;AAEtC,UAAO;;AAIT,SAAO;;CAGT,OAAMD,qBACJ,MACwE;AAGxE,MAAI,KAAK,iBAAiB,kBACxB,QAAO;AAIT,MAAI,gBAAgB,KAIlB,QAAO,MADkB,2BAFV,qBAAqB,KAEsB,CAAC;AAK7D,MAAI,gBAAgB,eAGlB,QAAO,MAFkB,2BAA2B,KAAK;AAK3D,SAAO;;CAGT,OAAML,cACJ,MACA,YAKA,MACe;EACf,MAAM,WAAW,MAAM,MAAKI,cAAe,WAAW,KAAK;EAC3D,MAAM,cAAc;GAClB,QAAQ,KAAK,iBAAiB;GAC9B,MAAM,WAAW;GACjB,MAAM,WAAW;GACjB,MAAM;GACP;EAID,MAAM,SAAS,KAAK,QAAQ,YAAY;AAKxC,MACE,KAAK,iBAAiB,qBACtB,SAAS,UACT,OAAO,EAEP,MAAK,aAAa,QAAQ,EACxB,KAAK,EAAE,YAAY,MAAM,EAC1B,CAAC;;CAIN,aAAa,MAAY,SAA+B;AACtD,OAAK,QAAQ,CAAC,OAAO,UAAU;AAC7B,YAAS,UAAU,IAAI,MAAM,MAAKD,oBAAqB,MAAM,CAAC,CAAC;IAC/D;;CAGJ,uBACE,MACA,QACA,eACA,UACiB;AACjB,SAAO;GACL,cAAc;AACZ,SAAK,WAAW;;GAElB,aAAa;AACX,QAAI,OACF,MAAK,YAAY,OAAO;;GAG5B,cAAc;AACZ,QAAI,OACF,MAAK,YAAY,OAAO;;GAG5B,QAAQ;GACR,UAAU,OAAO,OAAO,EAAE,GAAG,UAAU,CAAC;GACzC;;CAGH,UAAgB"}
|
package/dist/esm/upload/car.js
CHANGED
|
@@ -73,8 +73,8 @@ async function* fileSource(files, onProgress, signal) {
|
|
|
73
73
|
async function preprocessToCar(input, options) {
|
|
74
74
|
let files;
|
|
75
75
|
if (input instanceof ReadableStream) {
|
|
76
|
-
const [streamForSize
|
|
77
|
-
await calculateStreamSize(streamForSize
|
|
76
|
+
const [streamForSize, streamForFile] = input.tee();
|
|
77
|
+
await calculateStreamSize(streamForSize, options?.signal);
|
|
78
78
|
const streamBlob = await streamToBlob(streamForFile, "application/octet-stream");
|
|
79
79
|
files = [new File([streamBlob], options?.name || "upload", { type: streamBlob.type })];
|
|
80
80
|
} else if (Array.isArray(input)) files = input;
|
|
@@ -108,7 +108,7 @@ async function preprocessToCar(input, options) {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
async function isCarFile(file) {
|
|
111
|
-
if (file.type !==
|
|
111
|
+
if (file.type !== "application/vnd.ipld.car" && !file.name.endsWith(".car")) return false;
|
|
112
112
|
try {
|
|
113
113
|
const iterable = readableStreamToAsyncIterable(file.stream());
|
|
114
114
|
return (await (await CarReader.fromIterable(iterable)).getRoots()).length > 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"car.js","names":["createUnstorageDatastore"
|
|
1
|
+
{"version":3,"file":"car.js","names":["createUnstorageDatastore"],"sources":["../../../src/upload/car.ts"],"sourcesContent":["import { car } from \"@helia/car\";\nimport { createHeliaHTTP } from \"@helia/http\";\nimport { unixfs } from \"@helia/unixfs\";\nimport {\n createBlockstore,\n createDatastore as createUnstorageDatastore,\n} from \"@/blockstore\";\nimport type { CID } from \"multiformats/cid\";\nimport { CarReader } from \"@ipld/car\";\nimport type { Datastore } from \"interface-datastore\";\n\nimport {\n asyncGeneratorToReadableStream,\n calculateStreamSize,\n readableStreamToAsyncIterable,\n streamToBlob,\n} from \"@/utils/stream\";\nimport { FILE_EXTENSION_CAR, MIME_TYPE_CAR } from \"@/types/mime-types\";\n\nexport interface CarPreprocessOptions {\n name?: string;\n onProgress?: (percentage: number) => void;\n signal?: AbortSignal;\n}\n\nexport interface CarPreprocessResult {\n carStream: ReadableStream<Uint8Array>;\n rootCid: string;\n size: bigint;\n}\n\nexport interface CarConfig {\n /**\n * Custom datastore instance for Helia.\n * If provided, this datastore will be used directly without creating one from storage.\n * Highest priority - takes precedence over storage and datastoreName.\n */\n datastore?: Datastore;\n\n /**\n * Custom base name for Helia storage.\n * Passed as the `base` option to both blockstore and datastore storage instances.\n * Only used when datastore is not provided.\n * @default \"pinner-helia-data\"\n */\n datastoreName?: string;\n}\n\nlet helia: any = null;\nlet blockstore: any = null;\nlet datastore: any = null;\nlet config: CarConfig = {};\n\nexport function configureCar(carConfig: CarConfig) {\n config = carConfig;\n}\n\nasync function getHelia() {\n if (helia) return helia;\n\n const BlockstoreClass = createBlockstore();\n const DatastoreClass = createUnstorageDatastore();\n\n blockstore = new BlockstoreClass({\n prefix: \"pinner-helia-blocks\",\n base: config.datastoreName,\n });\n datastore =\n config.datastore ||\n new DatastoreClass({\n prefix: \"pinner-helia-data\",\n base: config.datastoreName,\n });\n\n helia = await createHeliaHTTP({\n blockstore,\n datastore,\n });\n\n return helia;\n}\n\nasync function cleanupHelia() {\n if (datastore?.close) {\n await datastore.close();\n }\n helia = null;\n blockstore = null;\n datastore = null;\n}\n\nasync function* fileSource(\n files: File[],\n onProgress?: (percentage: number) => void,\n signal?: AbortSignal,\n): AsyncGenerator<{\n content: AsyncIterable<Uint8Array> | undefined;\n path: string;\n}> {\n const seenDirs = new Set<string>();\n let totalBytes = 0n;\n let processedBytes = 0n;\n\n for (const file of files) {\n totalBytes += BigInt(file.size);\n }\n\n for (const file of files) {\n if (signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n\n const fullPath = (file as any).webkitRelativePath ?? file.name;\n\n if (fullPath.includes(\"/.\")) {\n continue;\n }\n\n const parts = fullPath.split(\"/\").filter((part: string) => part.length > 0);\n\n for (let i = 1; i < parts.length; i++) {\n if (signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n\n const dirPath = parts.slice(0, i).join(\"/\");\n\n if (!seenDirs.has(dirPath)) {\n seenDirs.add(dirPath);\n yield {\n content: (async function* () {})(),\n path: dirPath,\n };\n }\n }\n\n yield {\n content: readableStreamToAsyncIterable(file.stream()),\n path: fullPath,\n };\n\n if (onProgress && totalBytes > 0n) {\n processedBytes += BigInt(file.size);\n const progressPercent = Number((processedBytes * 100n) / totalBytes);\n onProgress(progressPercent);\n }\n }\n}\n\nexport async function preprocessToCar(\n input: File | ReadableStream<Uint8Array> | File[],\n options?: CarPreprocessOptions,\n): Promise<CarPreprocessResult> {\n let files: File[];\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForFile] = input.tee();\n const size = await calculateStreamSize(streamForSize, options?.signal);\n const streamBlob = await streamToBlob(\n streamForFile,\n \"application/octet-stream\",\n );\n files = [\n new File([streamBlob], options?.name || \"upload\", {\n type: streamBlob.type,\n }),\n ];\n } else if (Array.isArray(input)) {\n files = input;\n } else {\n files = [input];\n }\n\n const heliaInstance = await getHelia();\n const fs = unixfs(heliaInstance);\n const c = car(heliaInstance);\n\n let rootCid: CID | undefined;\n let blocksCount = 0n;\n\n const src = fileSource(files, options?.onProgress, options?.signal);\n\n let hasFiles = false;\n for await (const result of fs.addAll(src, {\n cidVersion: 1,\n rawLeaves: false,\n signal: options?.signal,\n onProgress(event) {\n if (event.type === \"blocks:put:blockstore:put\") {\n blocksCount++;\n }\n },\n })) {\n if (options?.signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n rootCid = result.cid;\n hasFiles = true;\n }\n\n if (!hasFiles || !rootCid) {\n throw new Error(\"No files to process\");\n }\n\n // c.export() now returns an async generator directly (was renamed from 'stream')\n const carAsyncGenerator = c.export(rootCid!, { signal: options?.signal });\n const carStream = asyncGeneratorToReadableStream(carAsyncGenerator);\n\n // Use stream tee to create two identical streams - one for size calculation, one for processing\n const [streamForSize, streamForProcessing] = carStream.tee();\n\n const size = await calculateStreamSize(streamForSize, options?.signal);\n\n return {\n carStream: streamForProcessing,\n rootCid: rootCid!.toString(),\n size,\n };\n}\n\nexport async function isCarFile(file: File): Promise<boolean> {\n if (file.type !== MIME_TYPE_CAR && !file.name.endsWith(FILE_EXTENSION_CAR)) {\n return false;\n }\n\n try {\n const iterable = readableStreamToAsyncIterable(file.stream());\n const reader = await CarReader.fromIterable(iterable);\n const roots = await reader.getRoots();\n return roots.length > 0;\n } catch {\n return false;\n }\n}\n\nexport async function destroyCarPreprocessor() {\n await cleanupHelia();\n}\n"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ var UploadManager = class {
|
|
|
19
19
|
constructor(config) {
|
|
20
20
|
this.xhrHandler = new XHRUploadHandler(config);
|
|
21
21
|
this.tusHandler = new TUSUploadHandler(config);
|
|
22
|
-
this.portalSdk = new Sdk(config.endpoint ||
|
|
22
|
+
this.portalSdk = new Sdk(config.endpoint || "https://ipfs.pinner.xyz");
|
|
23
23
|
configureCar({
|
|
24
24
|
datastoreName: config.datastoreName,
|
|
25
25
|
datastore: config.datastore
|
|
@@ -165,7 +165,7 @@ var UploadManager = class {
|
|
|
165
165
|
if (size >= BigInt(limit)) return this.#uploadFile({
|
|
166
166
|
data: streamForUpload,
|
|
167
167
|
name: options?.name || "upload",
|
|
168
|
-
type: options?.name?.endsWith(
|
|
168
|
+
type: options?.name?.endsWith(".car") || options?.isDirectory ? MIME_TYPE_CAR : MIME_TYPE_OCTET_STREAM,
|
|
169
169
|
size: Number(size)
|
|
170
170
|
}, options);
|
|
171
171
|
else {
|
|
@@ -180,9 +180,9 @@ var UploadManager = class {
|
|
|
180
180
|
if (options?.isCarFile === true) return true;
|
|
181
181
|
if (options?.isCarFile === false) return false;
|
|
182
182
|
if (input instanceof File) {
|
|
183
|
-
if (input.type ===
|
|
183
|
+
if (input.type === "application/vnd.ipld.car" || input.name.endsWith(".car")) return await isCarFile(input);
|
|
184
184
|
}
|
|
185
|
-
if (input instanceof ReadableStream && options?.name?.endsWith(
|
|
185
|
+
if (input instanceof ReadableStream && options?.name?.endsWith(".car")) return options?.isCarFile !== false;
|
|
186
186
|
return false;
|
|
187
187
|
}
|
|
188
188
|
async #uploadCarResult(carResult, name, options) {
|
|
@@ -218,7 +218,7 @@ var UploadManager = class {
|
|
|
218
218
|
return this.#uploadFile(file, options);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
if (input.type !==
|
|
221
|
+
if (input.type !== "application/vnd.ipld.car") input = new File([input], input.name, {
|
|
222
222
|
type: MIME_TYPE_CAR,
|
|
223
223
|
lastModified: input.lastModified
|
|
224
224
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":["#validateInput","#uploadInput","#uploadCarFile","#waitForOperationByCid","#uploadCarResult","#isCarFileUpload","#uploadFile"],"sources":["../../../src/upload/manager.ts"],"sourcesContent":["import type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport {\n OPERATION_STATUS,\n type OperationsQueryParams,\n Sdk,\n} from \"@lumeweb/portal-sdk\";\nimport { createEqFilter } from \"@lumeweb/query-builder\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadResult,\n} from \"@/types/upload\";\nimport { isUploadResult } from \"@/types/upload\";\nimport { XHRUploadHandler } from \"./xhr-upload\";\nimport { TUSUploadHandler } from \"./tus-upload\";\nimport { DEFAULT_ENDPOINT, TUS_SIZE_THRESHOLD } from \"@/types/constants\";\nimport {\n FILE_EXTENSION_CAR,\n MIME_TYPE_CAR,\n MIME_TYPE_OCTET_STREAM,\n} from \"@/types/mime-types\";\nimport {\n type CarPreprocessResult,\n configureCar,\n destroyCarPreprocessor,\n isCarFile,\n preprocessToCar,\n} from \"./car\";\nimport { calculateStreamSize, streamToBlob } from \"../utils/stream\";\nimport { EmptyFileError } from \"../errors\";\nimport { type UploadInputObject } from \"./normalize\";\n\nexport class UploadManager {\n private xhrHandler: XHRUploadHandler;\n private tusHandler: TUSUploadHandler;\n private portalSdk: Sdk;\n private uploadLimit: number = TUS_SIZE_THRESHOLD; // Default to 100 MB\n private limitFetched: boolean = false;\n\n constructor(config: PinnerConfig) {\n this.xhrHandler = new XHRUploadHandler(config);\n this.tusHandler = new TUSUploadHandler(config);\n this.portalSdk = new Sdk(config.endpoint || DEFAULT_ENDPOINT);\n configureCar({\n datastoreName: config.datastoreName,\n datastore: config.datastore,\n });\n }\n\n async fetchUploadLimit(): Promise<number> {\n if (this.limitFetched) {\n return this.uploadLimit;\n }\n\n try {\n const result = await this.portalSdk.account().uploadLimit();\n if (result.success && result.data?.limit) {\n this.uploadLimit = result.data.limit;\n }\n } catch {\n // Fallback to default 100 MB if API fails\n this.uploadLimit = TUS_SIZE_THRESHOLD;\n }\n\n this.limitFetched = true;\n return this.uploadLimit;\n }\n\n getUploadLimit(): number {\n return this.uploadLimit;\n }\n\n async upload(\n input: UploadInput,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n this.#validateInput(input, options);\n return this.#uploadInput(input, options);\n }\n\n async uploadCar(\n input: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n this.#validateInput(input, options);\n return this.#uploadCarFile(input, options);\n }\n\n /**\n * Wait for an operation to complete or reach a settled state.\n *\n * Handles two scenarios:\n * 1. If an operationId is provided (in UploadResult), uses it directly\n * 2. If only CID is available, lists operations filtered by CID and polls the first result\n *\n * @param input Either an operation ID (number) or an UploadResult\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async waitForOperation(\n input: number | UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n // Scenario 1: UploadResult with operationId - use it directly\n if (isUploadResult(input) && input.operationId) {\n const result = await this.portalSdk\n .account()\n .waitForOperation(input.operationId, options);\n\n if (!result.success) {\n throw new Error(\n result.error?.message || `Operation ${input.operationId} failed`,\n );\n }\n\n const operation = result.data;\n if (!operation.cid) {\n throw new Error(`Operation ${input.operationId} completed without CID`);\n }\n\n if (\n operation.status?.toLowerCase() === OPERATION_STATUS.FAILED ||\n operation.status?.toLowerCase() === OPERATION_STATUS.ERROR\n ) {\n throw new Error(\n `Operation ${input.operationId} failed: ${operation.error || operation.status_message || \"Unknown error\"}`,\n );\n }\n\n // Merge operation data into the original upload result\n return {\n ...input,\n cid: operation.cid,\n operationId: operation.id,\n };\n }\n\n // Scenario 2: UploadResult with CID but no operationId - find by CID\n if (isUploadResult(input) && input.cid) {\n return await this.#waitForOperationByCid(input, options);\n }\n\n // Scenario 3: Only operation ID provided\n const operationId = typeof input === \"number\" ? input : undefined;\n if (operationId) {\n const result = await this.portalSdk\n .account()\n .waitForOperation(operationId, options);\n\n if (!result.success) {\n throw new Error(\n result.error?.message || `Operation ${operationId} failed`,\n );\n }\n\n const operation = result.data;\n if (!operation.cid) {\n throw new Error(`Operation ${operationId} completed without CID`);\n }\n\n if (\n operation.status?.toLowerCase() === OPERATION_STATUS.FAILED ||\n operation.status?.toLowerCase() === OPERATION_STATUS.ERROR\n ) {\n throw new Error(\n `Operation ${operationId} failed: ${operation.error || operation.status_message || \"Unknown error\"}`,\n );\n }\n\n return {\n id: operationId.toString(),\n cid: operation.cid,\n name: operation.operation_display_name || \"Unknown\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(operation.started_at),\n numberOfFiles: 1,\n operationId: operation.id,\n };\n }\n\n throw new Error(\n \"No operation ID or CID provided, cannot wait for operation\",\n );\n }\n\n /**\n * Wait for an operation by CID.\n *\n * This is used when we have a CID from an upload but no operation ID.\n * We list operations filtered by CID to find the operation ID,\n * then use the SDK's waitForOperation for polling.\n *\n * @param uploadResult UploadResult with CID\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async #waitForOperationByCid(\n uploadResult: UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n // List operations filtered by CID\n const params: OperationsQueryParams = {\n filters: [createEqFilter(\"cid\", uploadResult.cid)],\n pagination: { start: 0, end: 1 },\n };\n\n const result = await this.portalSdk.account().listOperations(params);\n\n if (!result.success) {\n throw new Error(`Failed to find operation with CID ${uploadResult.cid}`);\n }\n\n const operation = result.data.data?.[0];\n if (!operation) {\n throw new Error(`Failed to find operation with CID ${uploadResult.cid}`);\n }\n\n const operationId = operation.id;\n\n // Use SDK's waitForOperation for polling\n const waitResult = await this.portalSdk\n .account()\n .waitForOperation(operationId, options);\n\n if (!waitResult.success) {\n throw new Error(\n waitResult.error?.message || `Operation ${operationId} failed`,\n );\n }\n\n const finalOperation = waitResult.data;\n if (!finalOperation.cid) {\n throw new Error(`Operation ${operationId} completed without CID`);\n }\n\n if (\n finalOperation.status?.toLowerCase() === OPERATION_STATUS.FAILED ||\n finalOperation.status?.toLowerCase() === OPERATION_STATUS.ERROR\n ) {\n throw new Error(\n `Operation ${operationId} failed: ${finalOperation.error || finalOperation.status_message || \"Unknown error\"}`,\n );\n }\n\n // Return merged result\n return {\n ...uploadResult,\n cid: finalOperation.cid,\n operationId: finalOperation.id,\n };\n }\n\n #validateInput(input: UploadInput, options?: UploadOptions): void {\n if (input instanceof File) {\n if (input.size === 0) {\n throw new EmptyFileError(`Cannot upload empty file: ${input.name}`);\n }\n } else if (input instanceof ReadableStream) {\n // For ReadableStream, we can only validate if size is provided\n // Otherwise we need to calculate the size which consumes the stream\n if (options?.size !== undefined && options.size === 0) {\n throw new EmptyFileError(\"Cannot upload empty stream\");\n }\n }\n }\n\n async uploadDirectory(\n files: File[],\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const carResult = await preprocessToCar(files, {\n onProgress: options?.onProgress\n ? (p) =>\n options.onProgress!({\n percentage: p,\n bytesUploaded: 0,\n bytesTotal: 0,\n })\n : undefined,\n signal: options?.signal,\n });\n\n const operation = await this.#uploadCarResult(\n carResult,\n options?.name || \"directory\",\n options,\n );\n\n if (options?.waitForOperation && operation.result) {\n const uploadResult = await operation.result;\n operation.result = this.waitForOperation(\n { ...uploadResult, isDirectory: true, numberOfFiles: files.length },\n options.operationPollingOptions,\n );\n }\n\n return operation;\n }\n\n async #uploadInput(\n input: UploadInput,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n // Check if this is a CAR file that should be uploaded without preprocessing\n const isCarUpload = await this.#isCarFileUpload(input, options);\n if (isCarUpload) {\n return this.#uploadCarFile(input, options);\n }\n\n const limit = await this.fetchUploadLimit();\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForUpload] = input.tee();\n let size: bigint;\n if (options?.size !== undefined) {\n size = BigInt(options.size);\n } else {\n size = await calculateStreamSize(streamForSize, options?.signal);\n }\n\n if (size >= BigInt(limit)) {\n return this.#uploadFile(\n {\n data: streamForUpload,\n name: options?.name || \"upload\",\n type:\n options?.name?.endsWith(FILE_EXTENSION_CAR) ||\n options?.isDirectory\n ? MIME_TYPE_CAR\n : MIME_TYPE_OCTET_STREAM,\n size: Number(size),\n },\n options,\n );\n } else {\n const blob = await streamToBlob(\n streamForUpload,\n \"application/octet-stream\",\n );\n const file = new File([blob], options?.name || \"upload\", {\n type: blob.type,\n });\n return this.#uploadFile(file, options);\n }\n }\n\n return this.#uploadFile(input, options);\n }\n\n async #isCarFileUpload(\n input: UploadInput,\n options?: UploadOptions,\n ): Promise<boolean> {\n // Explicit option takes precedence\n if (options?.isCarFile === true) {\n return true;\n }\n if (options?.isCarFile === false) {\n return false;\n }\n\n // Check if File input is a valid CAR file\n if (input instanceof File) {\n // Quick check: MIME type or extension\n if (\n input.type === MIME_TYPE_CAR ||\n input.name.endsWith(FILE_EXTENSION_CAR)\n ) {\n // Verify it's actually a valid CAR file\n return await isCarFile(input);\n }\n }\n\n // For ReadableStream, rely on explicit isCarFile option or name extension\n if (\n input instanceof ReadableStream &&\n options?.name?.endsWith(FILE_EXTENSION_CAR)\n ) {\n // We can't verify stream content without consuming it,\n // so we trust the explicit isCarFile option or extension\n return options?.isCarFile !== false;\n }\n\n return false;\n }\n\n async #uploadCarResult(\n carResult: CarPreprocessResult,\n name: string,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const limit = await this.fetchUploadLimit();\n\n if (carResult.size >= BigInt(limit)) {\n return this.#uploadFile(\n {\n data: carResult.carStream,\n name: `${name}${FILE_EXTENSION_CAR}`,\n type: MIME_TYPE_CAR,\n size: Number(carResult.size),\n },\n options,\n );\n } else {\n const blob = await streamToBlob(carResult.carStream, MIME_TYPE_CAR);\n const file = new File([blob], `${name}${FILE_EXTENSION_CAR}`, {\n type: MIME_TYPE_CAR,\n });\n return this.#uploadFile(file, options);\n }\n }\n\n async #uploadCarFile(\n input: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const limit = await this.fetchUploadLimit();\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForUpload] = input.tee();\n let size: bigint;\n if (options?.size !== undefined) {\n size = BigInt(options.size);\n } else {\n size = await calculateStreamSize(streamForSize, options?.signal);\n }\n\n if (size >= BigInt(limit)) {\n return this.#uploadFile(\n {\n data: streamForUpload,\n name: options?.name || \"upload.car\",\n type: MIME_TYPE_CAR,\n size: Number(size),\n },\n options,\n );\n } else {\n const blob = await streamToBlob(streamForUpload, MIME_TYPE_CAR);\n const file = new File([blob], options?.name || \"upload.car\", {\n type: MIME_TYPE_CAR,\n });\n return this.#uploadFile(file, options);\n }\n }\n\n // File input - ensure it has correct CAR MIME type\n if (input.type !== MIME_TYPE_CAR) {\n // Create a new File with correct CAR MIME type\n input = new File([input], input.name, {\n type: MIME_TYPE_CAR,\n lastModified: input.lastModified,\n });\n }\n\n return this.#uploadFile(input, options);\n }\n\n async #uploadFile(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const limit = await this.fetchUploadLimit();\n\n let isLargeFile = false;\n\n if (input instanceof File) {\n isLargeFile = input.size > limit;\n } else {\n isLargeFile = true;\n }\n\n const operation = await (isLargeFile\n ? this.tusHandler.upload(input, options)\n : this.xhrHandler.upload(input, options));\n\n if (options?.waitForOperation && operation.result) {\n const uploadResult = await operation.result;\n operation.result = this.waitForOperation(\n uploadResult,\n options.operationPollingOptions,\n );\n }\n\n return operation;\n }\n\n destroy(): void {\n this.xhrHandler.destroy();\n this.tusHandler.destroy();\n destroyCarPreprocessor();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manager.js","names":["#validateInput","#uploadInput","#uploadCarFile","#waitForOperationByCid","#uploadCarResult","#isCarFileUpload","#uploadFile"],"sources":["../../../src/upload/manager.ts"],"sourcesContent":["import type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport {\n OPERATION_STATUS,\n type OperationsQueryParams,\n Sdk,\n} from \"@lumeweb/portal-sdk\";\nimport { createEqFilter } from \"@lumeweb/query-builder\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadResult,\n} from \"@/types/upload\";\nimport { isUploadResult } from \"@/types/upload\";\nimport { XHRUploadHandler } from \"./xhr-upload\";\nimport { TUSUploadHandler } from \"./tus-upload\";\nimport { DEFAULT_ENDPOINT, TUS_SIZE_THRESHOLD } from \"@/types/constants\";\nimport {\n FILE_EXTENSION_CAR,\n MIME_TYPE_CAR,\n MIME_TYPE_OCTET_STREAM,\n} from \"@/types/mime-types\";\nimport {\n type CarPreprocessResult,\n configureCar,\n destroyCarPreprocessor,\n isCarFile,\n preprocessToCar,\n} from \"./car\";\nimport { calculateStreamSize, streamToBlob } from \"../utils/stream\";\nimport { EmptyFileError } from \"../errors\";\nimport { type UploadInputObject } from \"./normalize\";\n\nexport class UploadManager {\n private xhrHandler: XHRUploadHandler;\n private tusHandler: TUSUploadHandler;\n private portalSdk: Sdk;\n private uploadLimit: number = TUS_SIZE_THRESHOLD; // Default to 100 MB\n private limitFetched: boolean = false;\n\n constructor(config: PinnerConfig) {\n this.xhrHandler = new XHRUploadHandler(config);\n this.tusHandler = new TUSUploadHandler(config);\n this.portalSdk = new Sdk(config.endpoint || DEFAULT_ENDPOINT);\n configureCar({\n datastoreName: config.datastoreName,\n datastore: config.datastore,\n });\n }\n\n async fetchUploadLimit(): Promise<number> {\n if (this.limitFetched) {\n return this.uploadLimit;\n }\n\n try {\n const result = await this.portalSdk.account().uploadLimit();\n if (result.success && result.data?.limit) {\n this.uploadLimit = result.data.limit;\n }\n } catch {\n // Fallback to default 100 MB if API fails\n this.uploadLimit = TUS_SIZE_THRESHOLD;\n }\n\n this.limitFetched = true;\n return this.uploadLimit;\n }\n\n getUploadLimit(): number {\n return this.uploadLimit;\n }\n\n async upload(\n input: UploadInput,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n this.#validateInput(input, options);\n return this.#uploadInput(input, options);\n }\n\n async uploadCar(\n input: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n this.#validateInput(input, options);\n return this.#uploadCarFile(input, options);\n }\n\n /**\n * Wait for an operation to complete or reach a settled state.\n *\n * Handles two scenarios:\n * 1. If an operationId is provided (in UploadResult), uses it directly\n * 2. If only CID is available, lists operations filtered by CID and polls the first result\n *\n * @param input Either an operation ID (number) or an UploadResult\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async waitForOperation(\n input: number | UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n // Scenario 1: UploadResult with operationId - use it directly\n if (isUploadResult(input) && input.operationId) {\n const result = await this.portalSdk\n .account()\n .waitForOperation(input.operationId, options);\n\n if (!result.success) {\n throw new Error(\n result.error?.message || `Operation ${input.operationId} failed`,\n );\n }\n\n const operation = result.data;\n if (!operation.cid) {\n throw new Error(`Operation ${input.operationId} completed without CID`);\n }\n\n if (\n operation.status?.toLowerCase() === OPERATION_STATUS.FAILED ||\n operation.status?.toLowerCase() === OPERATION_STATUS.ERROR\n ) {\n throw new Error(\n `Operation ${input.operationId} failed: ${operation.error || operation.status_message || \"Unknown error\"}`,\n );\n }\n\n // Merge operation data into the original upload result\n return {\n ...input,\n cid: operation.cid,\n operationId: operation.id,\n };\n }\n\n // Scenario 2: UploadResult with CID but no operationId - find by CID\n if (isUploadResult(input) && input.cid) {\n return await this.#waitForOperationByCid(input, options);\n }\n\n // Scenario 3: Only operation ID provided\n const operationId = typeof input === \"number\" ? input : undefined;\n if (operationId) {\n const result = await this.portalSdk\n .account()\n .waitForOperation(operationId, options);\n\n if (!result.success) {\n throw new Error(\n result.error?.message || `Operation ${operationId} failed`,\n );\n }\n\n const operation = result.data;\n if (!operation.cid) {\n throw new Error(`Operation ${operationId} completed without CID`);\n }\n\n if (\n operation.status?.toLowerCase() === OPERATION_STATUS.FAILED ||\n operation.status?.toLowerCase() === OPERATION_STATUS.ERROR\n ) {\n throw new Error(\n `Operation ${operationId} failed: ${operation.error || operation.status_message || \"Unknown error\"}`,\n );\n }\n\n return {\n id: operationId.toString(),\n cid: operation.cid,\n name: operation.operation_display_name || \"Unknown\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(operation.started_at),\n numberOfFiles: 1,\n operationId: operation.id,\n };\n }\n\n throw new Error(\n \"No operation ID or CID provided, cannot wait for operation\",\n );\n }\n\n /**\n * Wait for an operation by CID.\n *\n * This is used when we have a CID from an upload but no operation ID.\n * We list operations filtered by CID to find the operation ID,\n * then use the SDK's waitForOperation for polling.\n *\n * @param uploadResult UploadResult with CID\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async #waitForOperationByCid(\n uploadResult: UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n // List operations filtered by CID\n const params: OperationsQueryParams = {\n filters: [createEqFilter(\"cid\", uploadResult.cid)],\n pagination: { start: 0, end: 1 },\n };\n\n const result = await this.portalSdk.account().listOperations(params);\n\n if (!result.success) {\n throw new Error(`Failed to find operation with CID ${uploadResult.cid}`);\n }\n\n const operation = result.data.data?.[0];\n if (!operation) {\n throw new Error(`Failed to find operation with CID ${uploadResult.cid}`);\n }\n\n const operationId = operation.id;\n\n // Use SDK's waitForOperation for polling\n const waitResult = await this.portalSdk\n .account()\n .waitForOperation(operationId, options);\n\n if (!waitResult.success) {\n throw new Error(\n waitResult.error?.message || `Operation ${operationId} failed`,\n );\n }\n\n const finalOperation = waitResult.data;\n if (!finalOperation.cid) {\n throw new Error(`Operation ${operationId} completed without CID`);\n }\n\n if (\n finalOperation.status?.toLowerCase() === OPERATION_STATUS.FAILED ||\n finalOperation.status?.toLowerCase() === OPERATION_STATUS.ERROR\n ) {\n throw new Error(\n `Operation ${operationId} failed: ${finalOperation.error || finalOperation.status_message || \"Unknown error\"}`,\n );\n }\n\n // Return merged result\n return {\n ...uploadResult,\n cid: finalOperation.cid,\n operationId: finalOperation.id,\n };\n }\n\n #validateInput(input: UploadInput, options?: UploadOptions): void {\n if (input instanceof File) {\n if (input.size === 0) {\n throw new EmptyFileError(`Cannot upload empty file: ${input.name}`);\n }\n } else if (input instanceof ReadableStream) {\n // For ReadableStream, we can only validate if size is provided\n // Otherwise we need to calculate the size which consumes the stream\n if (options?.size !== undefined && options.size === 0) {\n throw new EmptyFileError(\"Cannot upload empty stream\");\n }\n }\n }\n\n async uploadDirectory(\n files: File[],\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const carResult = await preprocessToCar(files, {\n onProgress: options?.onProgress\n ? (p) =>\n options.onProgress!({\n percentage: p,\n bytesUploaded: 0,\n bytesTotal: 0,\n })\n : undefined,\n signal: options?.signal,\n });\n\n const operation = await this.#uploadCarResult(\n carResult,\n options?.name || \"directory\",\n options,\n );\n\n if (options?.waitForOperation && operation.result) {\n const uploadResult = await operation.result;\n operation.result = this.waitForOperation(\n { ...uploadResult, isDirectory: true, numberOfFiles: files.length },\n options.operationPollingOptions,\n );\n }\n\n return operation;\n }\n\n async #uploadInput(\n input: UploadInput,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n // Check if this is a CAR file that should be uploaded without preprocessing\n const isCarUpload = await this.#isCarFileUpload(input, options);\n if (isCarUpload) {\n return this.#uploadCarFile(input, options);\n }\n\n const limit = await this.fetchUploadLimit();\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForUpload] = input.tee();\n let size: bigint;\n if (options?.size !== undefined) {\n size = BigInt(options.size);\n } else {\n size = await calculateStreamSize(streamForSize, options?.signal);\n }\n\n if (size >= BigInt(limit)) {\n return this.#uploadFile(\n {\n data: streamForUpload,\n name: options?.name || \"upload\",\n type:\n options?.name?.endsWith(FILE_EXTENSION_CAR) ||\n options?.isDirectory\n ? MIME_TYPE_CAR\n : MIME_TYPE_OCTET_STREAM,\n size: Number(size),\n },\n options,\n );\n } else {\n const blob = await streamToBlob(\n streamForUpload,\n \"application/octet-stream\",\n );\n const file = new File([blob], options?.name || \"upload\", {\n type: blob.type,\n });\n return this.#uploadFile(file, options);\n }\n }\n\n return this.#uploadFile(input, options);\n }\n\n async #isCarFileUpload(\n input: UploadInput,\n options?: UploadOptions,\n ): Promise<boolean> {\n // Explicit option takes precedence\n if (options?.isCarFile === true) {\n return true;\n }\n if (options?.isCarFile === false) {\n return false;\n }\n\n // Check if File input is a valid CAR file\n if (input instanceof File) {\n // Quick check: MIME type or extension\n if (\n input.type === MIME_TYPE_CAR ||\n input.name.endsWith(FILE_EXTENSION_CAR)\n ) {\n // Verify it's actually a valid CAR file\n return await isCarFile(input);\n }\n }\n\n // For ReadableStream, rely on explicit isCarFile option or name extension\n if (\n input instanceof ReadableStream &&\n options?.name?.endsWith(FILE_EXTENSION_CAR)\n ) {\n // We can't verify stream content without consuming it,\n // so we trust the explicit isCarFile option or extension\n return options?.isCarFile !== false;\n }\n\n return false;\n }\n\n async #uploadCarResult(\n carResult: CarPreprocessResult,\n name: string,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const limit = await this.fetchUploadLimit();\n\n if (carResult.size >= BigInt(limit)) {\n return this.#uploadFile(\n {\n data: carResult.carStream,\n name: `${name}${FILE_EXTENSION_CAR}`,\n type: MIME_TYPE_CAR,\n size: Number(carResult.size),\n },\n options,\n );\n } else {\n const blob = await streamToBlob(carResult.carStream, MIME_TYPE_CAR);\n const file = new File([blob], `${name}${FILE_EXTENSION_CAR}`, {\n type: MIME_TYPE_CAR,\n });\n return this.#uploadFile(file, options);\n }\n }\n\n async #uploadCarFile(\n input: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const limit = await this.fetchUploadLimit();\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForUpload] = input.tee();\n let size: bigint;\n if (options?.size !== undefined) {\n size = BigInt(options.size);\n } else {\n size = await calculateStreamSize(streamForSize, options?.signal);\n }\n\n if (size >= BigInt(limit)) {\n return this.#uploadFile(\n {\n data: streamForUpload,\n name: options?.name || \"upload.car\",\n type: MIME_TYPE_CAR,\n size: Number(size),\n },\n options,\n );\n } else {\n const blob = await streamToBlob(streamForUpload, MIME_TYPE_CAR);\n const file = new File([blob], options?.name || \"upload.car\", {\n type: MIME_TYPE_CAR,\n });\n return this.#uploadFile(file, options);\n }\n }\n\n // File input - ensure it has correct CAR MIME type\n if (input.type !== MIME_TYPE_CAR) {\n // Create a new File with correct CAR MIME type\n input = new File([input], input.name, {\n type: MIME_TYPE_CAR,\n lastModified: input.lastModified,\n });\n }\n\n return this.#uploadFile(input, options);\n }\n\n async #uploadFile(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const limit = await this.fetchUploadLimit();\n\n let isLargeFile = false;\n\n if (input instanceof File) {\n isLargeFile = input.size > limit;\n } else {\n isLargeFile = true;\n }\n\n const operation = await (isLargeFile\n ? this.tusHandler.upload(input, options)\n : this.xhrHandler.upload(input, options));\n\n if (options?.waitForOperation && operation.result) {\n const uploadResult = await operation.result;\n operation.result = this.waitForOperation(\n uploadResult,\n options.operationPollingOptions,\n );\n }\n\n return operation;\n }\n\n destroy(): void {\n this.xhrHandler.destroy();\n this.tusHandler.destroy();\n destroyCarPreprocessor();\n }\n}\n"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ function normalizeUploadInput(input, options) {
|
|
|
11
11
|
if (input instanceof ReadableStream) return {
|
|
12
12
|
data: input,
|
|
13
13
|
name: options?.name || "upload",
|
|
14
|
-
type: options?.name?.endsWith(
|
|
14
|
+
type: options?.name?.endsWith(".car") ? MIME_TYPE_CAR : MIME_TYPE_OCTET_STREAM,
|
|
15
15
|
size: 0
|
|
16
16
|
};
|
|
17
17
|
const objectInput = input;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.js","names":[],"sources":["../../../src/upload/normalize.ts"],"sourcesContent":["import type { UploadInput, UploadOptions } from \"@/types/upload\";\nimport {\n FILE_EXTENSION_CAR,\n MIME_TYPE_CAR,\n MIME_TYPE_OCTET_STREAM,\n} from \"@/types/mime-types\";\n\nexport interface NormalizedUploadInput {\n data: File | ReadableStream<Uint8Array>;\n name: string;\n type: string;\n size: number;\n}\n\nexport interface UploadInputObject {\n data: ReadableStream<Uint8Array>;\n name: string;\n type: string;\n size?: number;\n}\n\nexport function normalizeUploadInput(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n): NormalizedUploadInput {\n if (input instanceof File) {\n return {\n data: input,\n name: input.name,\n type: input.type,\n size: input.size,\n };\n }\n\n if (input instanceof ReadableStream) {\n return {\n data: input,\n name: options?.name || \"upload\",\n type: options?.name?.endsWith(FILE_EXTENSION_CAR)\n ? MIME_TYPE_CAR\n : MIME_TYPE_OCTET_STREAM,\n size: 0,\n };\n }\n\n const objectInput = input as UploadInputObject;\n return {\n data: objectInput.data,\n name: objectInput.name,\n type: objectInput.type,\n size: objectInput.size || 0,\n };\n}\n"],"mappings":";;;AAqBA,SAAgB,qBACd,OACA,SACuB;AACvB,KAAI,iBAAiB,KACnB,QAAO;EACL,MAAM;EACN,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,MAAM,MAAM;EACb;AAGH,KAAI,iBAAiB,eACnB,QAAO;EACL,MAAM;EACN,MAAM,SAAS,QAAQ;EACvB,MAAM,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"normalize.js","names":[],"sources":["../../../src/upload/normalize.ts"],"sourcesContent":["import type { UploadInput, UploadOptions } from \"@/types/upload\";\nimport {\n FILE_EXTENSION_CAR,\n MIME_TYPE_CAR,\n MIME_TYPE_OCTET_STREAM,\n} from \"@/types/mime-types\";\n\nexport interface NormalizedUploadInput {\n data: File | ReadableStream<Uint8Array>;\n name: string;\n type: string;\n size: number;\n}\n\nexport interface UploadInputObject {\n data: ReadableStream<Uint8Array>;\n name: string;\n type: string;\n size?: number;\n}\n\nexport function normalizeUploadInput(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n): NormalizedUploadInput {\n if (input instanceof File) {\n return {\n data: input,\n name: input.name,\n type: input.type,\n size: input.size,\n };\n }\n\n if (input instanceof ReadableStream) {\n return {\n data: input,\n name: options?.name || \"upload\",\n type: options?.name?.endsWith(FILE_EXTENSION_CAR)\n ? MIME_TYPE_CAR\n : MIME_TYPE_OCTET_STREAM,\n size: 0,\n };\n }\n\n const objectInput = input as UploadInputObject;\n return {\n data: objectInput.data,\n name: objectInput.name,\n type: objectInput.type,\n size: objectInput.size || 0,\n };\n}\n"],"mappings":";;;AAqBA,SAAgB,qBACd,OACA,SACuB;AACvB,KAAI,iBAAiB,KACnB,QAAO;EACL,MAAM;EACN,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,MAAM,MAAM;EACb;AAGH,KAAI,iBAAiB,eACnB,QAAO;EACL,MAAM;EACN,MAAM,SAAS,QAAQ;EACvB,MAAM,SAAS,MAAM,gBAA4B,GAC7C,gBACA;EACJ,MAAM;EACP;CAGH,MAAM,cAAc;AACpB,QAAO;EACL,MAAM,YAAY;EAClB,MAAM,YAAY;EAClB,MAAM,YAAY;EAClB,MAAM,YAAY,QAAQ;EAC3B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __require } from "../_virtual/
|
|
1
|
+
import { __require } from "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { isNodeEnvironment } from "./env.js";
|
|
3
3
|
|
|
4
4
|
//#region src/utils/tus-patch.ts
|
|
@@ -21,7 +21,7 @@ import { isNodeEnvironment } from "./env.js";
|
|
|
21
21
|
function patchTusNodeHttpStack() {
|
|
22
22
|
if (!isNodeEnvironment()) return;
|
|
23
23
|
try {
|
|
24
|
-
const tusHttpStackModule = __require("tus-js-client/lib.es5/node/httpStack");
|
|
24
|
+
const tusHttpStackModule = __require("tus-js-client/lib.es5/node/httpStack.js");
|
|
25
25
|
if (!tusHttpStackModule || !tusHttpStackModule.default) {
|
|
26
26
|
console.warn("[tus-patch] tus-js-client NodeHttpStack not found, patch skipped");
|
|
27
27
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tus-patch.js","names":[],"sources":["../../../src/utils/tus-patch.ts"],"sourcesContent":["/**\n * Runtime patch for tus-js-client's NodeHttpStack Request class.\n *\n * PROBLEM:\n * Code using onBeforeRequest hook may need to call abort() on the request\n * before send() is called. However, the Request class only sets this._request\n * inside the send() method, so getUnderlyingObject() returns null before send().\n *\n * SOLUTION:\n * Patch the Request class to initialize a dummy _request object with an abort()\n * method immediately when the request is created.\n *\n * NOTE:\n * This patch is only needed in Node.js environments. In browser environments,\n * the tus-js-client uses a different HTTP stack that doesn't have this issue.\n */\n\nimport { isNodeEnvironment } from \"./env\";\n\n// Track whether the patch has been applied to prevent multiple patches\nlet isPatched = false;\n\nexport function patchTusNodeHttpStack(): void {\n // Prevent multiple patches\n if (isPatched) {\n return;\n }\n\n // Only apply patch in Node.js environments\n if (!isNodeEnvironment()) {\n return;\n }\n\n // Find the tus-js-client NodeHttpStack module\n // It exports the default as NodeHttpStack with a nested Request class\n try {\n // Use dynamic require() to avoid bundling issues\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const tusHttpStackModule = require(\"tus-js-client/lib.es5/node/httpStack\");\n\n if (!tusHttpStackModule || !tusHttpStackModule.default) {\n console.warn(\n \"[tus-patch] tus-js-client NodeHttpStack not found, patch skipped\",\n );\n return;\n }\n\n const NodeHttpStack = tusHttpStackModule.default;\n\n // Get the original createRequest method\n const originalCreateRequest = NodeHttpStack.prototype.createRequest;\n\n if (typeof originalCreateRequest !== \"function\") {\n console.warn(\"[tus-patch] createRequest method not found, patch skipped\");\n return;\n }\n\n // Patch createRequest to set up a dummy _request on the Request instance\n NodeHttpStack.prototype.createRequest = function (\n method: string,\n url: string,\n ) {\n // Call the original createRequest to get the Request instance\n const request = originalCreateRequest.call(this, method, url);\n\n // Set a dummy _request object with an abort() method\n // This allows getUnderlyingObject().abort() to work before send() is called\n request._request = {\n abort: () => {\n // No-op abort before actual request is created\n // The real request will be created in send() and this will be replaced\n },\n // Preserve any existing properties that might be checked\n destroyed: false,\n };\n\n return request;\n };\n\n console.debug(\n \"[tus-patch] Successfully patched tus-js-client NodeHttpStack\",\n );\n } catch (error) {\n console.warn(\"[tus-patch] Failed to patch tus-js-client:\", error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,wBAA8B;AAO5C,KAAI,CAAC,mBAAmB,CACtB;AAKF,KAAI;EAGF,MAAM,+BAA6B,
|
|
1
|
+
{"version":3,"file":"tus-patch.js","names":[],"sources":["../../../src/utils/tus-patch.ts"],"sourcesContent":["/**\n * Runtime patch for tus-js-client's NodeHttpStack Request class.\n *\n * PROBLEM:\n * Code using onBeforeRequest hook may need to call abort() on the request\n * before send() is called. However, the Request class only sets this._request\n * inside the send() method, so getUnderlyingObject() returns null before send().\n *\n * SOLUTION:\n * Patch the Request class to initialize a dummy _request object with an abort()\n * method immediately when the request is created.\n *\n * NOTE:\n * This patch is only needed in Node.js environments. In browser environments,\n * the tus-js-client uses a different HTTP stack that doesn't have this issue.\n */\n\nimport { isNodeEnvironment } from \"./env\";\n\n// Track whether the patch has been applied to prevent multiple patches\nlet isPatched = false;\n\nexport function patchTusNodeHttpStack(): void {\n // Prevent multiple patches\n if (isPatched) {\n return;\n }\n\n // Only apply patch in Node.js environments\n if (!isNodeEnvironment()) {\n return;\n }\n\n // Find the tus-js-client NodeHttpStack module\n // It exports the default as NodeHttpStack with a nested Request class\n try {\n // Use dynamic require() to avoid bundling issues\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const tusHttpStackModule = require(\"tus-js-client/lib.es5/node/httpStack\");\n\n if (!tusHttpStackModule || !tusHttpStackModule.default) {\n console.warn(\n \"[tus-patch] tus-js-client NodeHttpStack not found, patch skipped\",\n );\n return;\n }\n\n const NodeHttpStack = tusHttpStackModule.default;\n\n // Get the original createRequest method\n const originalCreateRequest = NodeHttpStack.prototype.createRequest;\n\n if (typeof originalCreateRequest !== \"function\") {\n console.warn(\"[tus-patch] createRequest method not found, patch skipped\");\n return;\n }\n\n // Patch createRequest to set up a dummy _request on the Request instance\n NodeHttpStack.prototype.createRequest = function (\n method: string,\n url: string,\n ) {\n // Call the original createRequest to get the Request instance\n const request = originalCreateRequest.call(this, method, url);\n\n // Set a dummy _request object with an abort() method\n // This allows getUnderlyingObject().abort() to work before send() is called\n request._request = {\n abort: () => {\n // No-op abort before actual request is created\n // The real request will be created in send() and this will be replaced\n },\n // Preserve any existing properties that might be checked\n destroyed: false,\n };\n\n return request;\n };\n\n console.debug(\n \"[tus-patch] Successfully patched tus-js-client NodeHttpStack\",\n );\n } catch (error) {\n console.warn(\"[tus-patch] Failed to patch tus-js-client:\", error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,wBAA8B;AAO5C,KAAI,CAAC,mBAAmB,CACtB;AAKF,KAAI;EAGF,MAAM,+BAA6B,0CAAuC;AAE1E,MAAI,CAAC,sBAAsB,CAAC,mBAAmB,SAAS;AACtD,WAAQ,KACN,mEACD;AACD;;EAGF,MAAM,gBAAgB,mBAAmB;EAGzC,MAAM,wBAAwB,cAAc,UAAU;AAEtD,MAAI,OAAO,0BAA0B,YAAY;AAC/C,WAAQ,KAAK,4DAA4D;AACzE;;AAIF,gBAAc,UAAU,gBAAgB,SACtC,QACA,KACA;GAEA,MAAM,UAAU,sBAAsB,KAAK,MAAM,QAAQ,IAAI;AAI7D,WAAQ,WAAW;IACjB,aAAa;IAKb,WAAW;IACZ;AAED,UAAO;;AAGT,UAAQ,MACN,+DACD;UACM,OAAO;AACd,UAAQ,KAAK,8CAA8C,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","names":[],"sources":["../../../src/utils/validation.ts"],"sourcesContent":["/**\n * URL validation utilities to prevent SSRF and other security issues.\n */\n\nimport { ValidationError } from \"@/errors\";\nimport ipaddr from \"ipaddr.js\";\n\nexport { ValidationError };\n\n// IPv4 ranges to block\nconst BLOCKED_IPV4_RANGES = new Set([\n \"private\", // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16\n \"loopback\", // 127.0.0.0/8\n \"linkLocal\", // 169.254.0.0/16\n \"reserved\",\n \"broadcast\",\n \"carrierGradeNat\", // 100.64.0.0/10\n \"unspecified\", // 0.0.0.0/8\n]);\n\n// IPv6 ranges to block\nconst BLOCKED_IPV6_RANGES = new Set([\n \"uniqueLocal\", // fc00::/7\n \"loopback\", // ::1\n \"linkLocal\", // fe80::/10\n \"reserved\",\n \"multicast\", // ff00::/8\n \"ipv4Mapped\", // ::ffff:0:0/96\n \"unspecified\", // ::\n]);\n\n/**\n * Validates a URL string to ensure it's safe to fetch.\n * Only allows HTTP/HTTPS protocols and validates the URL format.\n *\n * @param urlString - The URL string to validate\n * @throws ValidationError if the URL is invalid or uses an unsafe protocol\n */\nexport function validateUrl(urlString: string): void {\n // Reject URLs with spaces or other invalid characters that shouldn't be in URLs\n if (/\\s/.test(urlString)) {\n throw new ValidationError(\n `Invalid URL: contains whitespace characters`,\n \"url\",\n );\n }\n\n try {\n const url = new URL(urlString);\n\n // Only allow HTTP and HTTPS protocols\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new ValidationError(\n `Invalid URL protocol: ${url.protocol}. Only http: and https: are allowed.`,\n \"url\",\n );\n }\n\n const hostname = url.hostname.toLowerCase();\n\n // Block localhost (case-insensitive)\n if (hostname === \"localhost\") {\n throw new ValidationError(\n \"Access to localhost addresses is not allowed\",\n \"url\",\n );\n }\n\n // Strip brackets from IPv6 addresses for ipaddr.js\n const cleanHostname = hostname.replace(/^\\[|\\]$/g, \"\");\n\n // Block IP addresses using ipaddr.js\n // This library handles alternative notations (decimal, octal, hex)\n // and comprehensive private range detection for both IPv4 and IPv6\n if (ipaddr.isValid(cleanHostname)) {\n const addr = ipaddr.parse(cleanHostname);\n\n // Check for IPv4 private ranges\n if (addr.kind() === \"ipv4\") {\n const ipv4Addr = addr as ipaddr.IPv4;\n const range = ipv4Addr.range();\n\n if (BLOCKED_IPV4_RANGES.has(range)) {\n throw new ValidationError(\n \"Access to private IP addresses is not allowed\",\n \"url\",\n );\n }\n }\n\n // Check for IPv6 private ranges\n if (addr.kind() === \"ipv6\") {\n const ipv6Addr = addr as ipaddr.IPv6;\n const range = ipv6Addr.range();\n\n if (BLOCKED_IPV6_RANGES.has(range)) {\n throw new ValidationError(\n \"Access to private IP addresses is not allowed\",\n \"url\",\n );\n }\n }\n }\n } catch (error) {\n if (error instanceof ValidationError) {\n throw error;\n }\n throw new ValidationError(\n `Invalid URL format: ${urlString}`,\n \"url\",\n error as Error,\n );\n }\n}\n\n/**\n * Validates a URL and returns the parsed URL object if valid.\n *\n * @param urlString - The URL string to validate\n * @returns The parsed URL object\n * @throws ValidationError if the URL is invalid\n */\nexport function parseValidatedUrl(urlString: string): URL {\n validateUrl(urlString);\n return new URL(urlString);\n}\n"],"mappings":";;;;;;;AAUA,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAGF,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AASF,SAAgB,YAAY,WAAyB;AAEnD,KAAI,KAAK,KAAK,UAAU,CACtB,OAAM,IAAI,gBACR,+CACA,MACD;AAGH,KAAI;EACF,MAAM,MAAM,IAAI,IAAI,UAAU;AAG9B,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAC/C,OAAM,IAAI,gBACR,yBAAyB,IAAI,SAAS,uCACtC,MACD;EAGH,MAAM,WAAW,IAAI,SAAS,aAAa;AAG3C,MAAI,aAAa,YACf,OAAM,IAAI,gBACR,gDACA,MACD;EAIH,MAAM,gBAAgB,SAAS,QAAQ,YAAY,GAAG;AAKtD,MAAI,OAAO,QAAQ,cAAc,EAAE;GACjC,MAAM,OAAO,OAAO,MAAM,cAAc;AAGxC,OAAI,KAAK,MAAM,KAAK,QAAQ;IAE1B,MAAM,
|
|
1
|
+
{"version":3,"file":"validation.js","names":["ipv4Addr","ipv6Addr"],"sources":["../../../src/utils/validation.ts"],"sourcesContent":["/**\n * URL validation utilities to prevent SSRF and other security issues.\n */\n\nimport { ValidationError } from \"@/errors\";\nimport ipaddr from \"ipaddr.js\";\n\nexport { ValidationError };\n\n// IPv4 ranges to block\nconst BLOCKED_IPV4_RANGES = new Set([\n \"private\", // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16\n \"loopback\", // 127.0.0.0/8\n \"linkLocal\", // 169.254.0.0/16\n \"reserved\",\n \"broadcast\",\n \"carrierGradeNat\", // 100.64.0.0/10\n \"unspecified\", // 0.0.0.0/8\n]);\n\n// IPv6 ranges to block\nconst BLOCKED_IPV6_RANGES = new Set([\n \"uniqueLocal\", // fc00::/7\n \"loopback\", // ::1\n \"linkLocal\", // fe80::/10\n \"reserved\",\n \"multicast\", // ff00::/8\n \"ipv4Mapped\", // ::ffff:0:0/96\n \"unspecified\", // ::\n]);\n\n/**\n * Validates a URL string to ensure it's safe to fetch.\n * Only allows HTTP/HTTPS protocols and validates the URL format.\n *\n * @param urlString - The URL string to validate\n * @throws ValidationError if the URL is invalid or uses an unsafe protocol\n */\nexport function validateUrl(urlString: string): void {\n // Reject URLs with spaces or other invalid characters that shouldn't be in URLs\n if (/\\s/.test(urlString)) {\n throw new ValidationError(\n `Invalid URL: contains whitespace characters`,\n \"url\",\n );\n }\n\n try {\n const url = new URL(urlString);\n\n // Only allow HTTP and HTTPS protocols\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new ValidationError(\n `Invalid URL protocol: ${url.protocol}. Only http: and https: are allowed.`,\n \"url\",\n );\n }\n\n const hostname = url.hostname.toLowerCase();\n\n // Block localhost (case-insensitive)\n if (hostname === \"localhost\") {\n throw new ValidationError(\n \"Access to localhost addresses is not allowed\",\n \"url\",\n );\n }\n\n // Strip brackets from IPv6 addresses for ipaddr.js\n const cleanHostname = hostname.replace(/^\\[|\\]$/g, \"\");\n\n // Block IP addresses using ipaddr.js\n // This library handles alternative notations (decimal, octal, hex)\n // and comprehensive private range detection for both IPv4 and IPv6\n if (ipaddr.isValid(cleanHostname)) {\n const addr = ipaddr.parse(cleanHostname);\n\n // Check for IPv4 private ranges\n if (addr.kind() === \"ipv4\") {\n const ipv4Addr = addr as ipaddr.IPv4;\n const range = ipv4Addr.range();\n\n if (BLOCKED_IPV4_RANGES.has(range)) {\n throw new ValidationError(\n \"Access to private IP addresses is not allowed\",\n \"url\",\n );\n }\n }\n\n // Check for IPv6 private ranges\n if (addr.kind() === \"ipv6\") {\n const ipv6Addr = addr as ipaddr.IPv6;\n const range = ipv6Addr.range();\n\n if (BLOCKED_IPV6_RANGES.has(range)) {\n throw new ValidationError(\n \"Access to private IP addresses is not allowed\",\n \"url\",\n );\n }\n }\n }\n } catch (error) {\n if (error instanceof ValidationError) {\n throw error;\n }\n throw new ValidationError(\n `Invalid URL format: ${urlString}`,\n \"url\",\n error as Error,\n );\n }\n}\n\n/**\n * Validates a URL and returns the parsed URL object if valid.\n *\n * @param urlString - The URL string to validate\n * @returns The parsed URL object\n * @throws ValidationError if the URL is invalid\n */\nexport function parseValidatedUrl(urlString: string): URL {\n validateUrl(urlString);\n return new URL(urlString);\n}\n"],"mappings":";;;;;;;AAUA,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAGF,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AASF,SAAgB,YAAY,WAAyB;AAEnD,KAAI,KAAK,KAAK,UAAU,CACtB,OAAM,IAAI,gBACR,+CACA,MACD;AAGH,KAAI;EACF,MAAM,MAAM,IAAI,IAAI,UAAU;AAG9B,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAC/C,OAAM,IAAI,gBACR,yBAAyB,IAAI,SAAS,uCACtC,MACD;EAGH,MAAM,WAAW,IAAI,SAAS,aAAa;AAG3C,MAAI,aAAa,YACf,OAAM,IAAI,gBACR,gDACA,MACD;EAIH,MAAM,gBAAgB,SAAS,QAAQ,YAAY,GAAG;AAKtD,MAAI,OAAO,QAAQ,cAAc,EAAE;GACjC,MAAM,OAAO,OAAO,MAAM,cAAc;AAGxC,OAAI,KAAK,MAAM,KAAK,QAAQ;IAE1B,MAAM,QAAQA,KAAS,OAAO;AAE9B,QAAI,oBAAoB,IAAI,MAAM,CAChC,OAAM,IAAI,gBACR,iDACA,MACD;;AAKL,OAAI,KAAK,MAAM,KAAK,QAAQ;IAE1B,MAAM,QAAQC,KAAS,OAAO;AAE9B,QAAI,oBAAoB,IAAI,MAAM,CAChC,OAAM,IAAI,gBACR,iDACA,MACD;;;UAIA,OAAO;AACd,MAAI,iBAAiB,gBACnB,OAAM;AAER,QAAM,IAAI,gBACR,uBAAuB,aACvB,OACA,MACD"}
|