@lumeweb/pinner 0.1.14 → 0.1.15
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/dist/esm/api/client.js +1 -1
- package/dist/esm/api/client.js.map +1 -1
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/key-exchange.js +76 -0
- package/dist/esm/auth/key-exchange.js.map +1 -0
- package/dist/esm/auth/manager.d.ts +7 -3
- package/dist/esm/auth/manager.js +4 -5
- package/dist/esm/auth/manager.js.map +1 -1
- package/dist/esm/config.js +13 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/pin/client.js +8 -8
- package/dist/esm/pin/client.js.map +1 -1
- package/dist/esm/pinner.js +5 -2
- package/dist/esm/pinner.js.map +1 -1
- package/dist/esm/upload/base-upload.js +3 -3
- package/dist/esm/upload/base-upload.js.map +1 -1
- package/dist/esm/upload/manager.d.ts +1 -0
- package/dist/esm/upload/manager.js +16 -2
- package/dist/esm/upload/manager.js.map +1 -1
- package/dist/esm/upload/tus-upload.js +2 -2
- package/dist/esm/upload/tus-upload.js.map +1 -1
- package/dist/esm/upload/xhr-upload.js +2 -2
- package/dist/esm/upload/xhr-upload.js.map +1 -1
- package/package.json +3 -2
package/dist/esm/api/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/api/client.ts"],"sourcesContent":["import ky, { HTTPError } from \"ky\";\nimport type { AuthManager } from \"@/auth\";\nimport {\n AuthenticationError,\n NotFoundError,\n NetworkError,\n ValidationError,\n} from \"@/errors\";\n\n/**\n * Shared base class for API clients that use ky with Bearer auth.\n *\n * Replaces the duplicated request() + error handling in IpnsClient and WebsitesClient.\n * Auth headers come from AuthManager — one source of truth.\n */\nexport abstract class ApiClient {\n protected readonly auth: AuthManager;\n protected readonly endpoint: string;\n\n constructor(auth: AuthManager, endpoint: string) {\n this.auth = auth;\n this.endpoint = endpoint;\n }\n\n protected async request<T>(\n path: string,\n options?: RequestInit & { signal?: AbortSignal },\n ): Promise<T> {\n try {\n const response = await ky(path, {\n prefix: this.endpoint,\n headers: {\n ...this.auth.getAuthHeaders(),\n \"Content-Type\": \"application/json\",\n },\n ...options,\n });\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (!text) {\n return undefined as T;\n }\n\n return JSON.parse(text) as T;\n } catch (error) {\n throw await this.mapError(error);\n }\n }\n\n protected async mapError(error: unknown): Promise<Error> {\n if (error instanceof HTTPError) {\n const status = error.response.status;\n const body = await error.response.json().catch(() => ({}));\n const message = (body as any).error || (body as any).message;\n\n if (status === 401 || status === 403) {\n return new AuthenticationError(message || \"Authentication failed\");\n }\n if (status === 404) {\n return new NotFoundError(message || \"Resource not found\");\n }\n if (status === 400) {\n return new ValidationError(message || \"Invalid request\");\n }\n if (status === 410) {\n return new ValidationError(message || \"Target is broken or gone\");\n }\n\n return new NetworkError(message || `HTTP error: ${status}`);\n }\n\n if (error instanceof Error) {\n return new NetworkError(error.message);\n }\n\n return new NetworkError(\"Unknown error occurred\");\n }\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAsB,YAAtB,MAAgC;CAC9B,AAAmB;CACnB,AAAmB;CAEnB,YAAY,MAAmB,UAAkB;AAC/C,OAAK,OAAO;AACZ,OAAK,WAAW;;CAGlB,MAAgB,QACd,MACA,SACY;AACZ,MAAI;GACF,MAAM,WAAW,MAAM,GAAG,MAAM;IAC9B,QAAQ,KAAK;IACb,SAAS;KACP,GAAG,KAAK,KAAK,gBAAgB;
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/api/client.ts"],"sourcesContent":["import ky, { HTTPError } from \"ky\";\nimport type { AuthManager } from \"@/auth\";\nimport {\n AuthenticationError,\n NotFoundError,\n NetworkError,\n ValidationError,\n} from \"@/errors\";\n\n/**\n * Shared base class for API clients that use ky with Bearer auth.\n *\n * Replaces the duplicated request() + error handling in IpnsClient and WebsitesClient.\n * Auth headers come from AuthManager — one source of truth.\n */\nexport abstract class ApiClient {\n protected readonly auth: AuthManager;\n protected readonly endpoint: string;\n\n constructor(auth: AuthManager, endpoint: string) {\n this.auth = auth;\n this.endpoint = endpoint;\n }\n\n protected async request<T>(\n path: string,\n options?: RequestInit & { signal?: AbortSignal },\n ): Promise<T> {\n try {\n const response = await ky(path, {\n prefix: this.endpoint,\n headers: {\n ...await this.auth.getAuthHeaders(),\n \"Content-Type\": \"application/json\",\n },\n ...options,\n });\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (!text) {\n return undefined as T;\n }\n\n return JSON.parse(text) as T;\n } catch (error) {\n throw await this.mapError(error);\n }\n }\n\n protected async mapError(error: unknown): Promise<Error> {\n if (error instanceof HTTPError) {\n const status = error.response.status;\n const body = await error.response.json().catch(() => ({}));\n const message = (body as any).error || (body as any).message;\n\n if (status === 401 || status === 403) {\n return new AuthenticationError(message || \"Authentication failed\");\n }\n if (status === 404) {\n return new NotFoundError(message || \"Resource not found\");\n }\n if (status === 400) {\n return new ValidationError(message || \"Invalid request\");\n }\n if (status === 410) {\n return new ValidationError(message || \"Target is broken or gone\");\n }\n\n return new NetworkError(message || `HTTP error: ${status}`);\n }\n\n if (error instanceof Error) {\n return new NetworkError(error.message);\n }\n\n return new NetworkError(\"Unknown error occurred\");\n }\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAsB,YAAtB,MAAgC;CAC9B,AAAmB;CACnB,AAAmB;CAEnB,YAAY,MAAmB,UAAkB;AAC/C,OAAK,OAAO;AACZ,OAAK,WAAW;;CAGlB,MAAgB,QACd,MACA,SACY;AACZ,MAAI;GACF,MAAM,WAAW,MAAM,GAAG,MAAM;IAC9B,QAAQ,KAAK;IACb,SAAS;KACP,GAAG,MAAM,KAAK,KAAK,gBAAgB;KACnC,gBAAgB;KACjB;IACD,GAAG;IACJ,CAAC;AAEF,OAAI,SAAS,WAAW,IACtB;GAGF,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,OAAI,CAAC,KACH;AAGF,UAAO,KAAK,MAAM,KAAK;WAChB,OAAO;AACd,SAAM,MAAM,KAAK,SAAS,MAAM;;;CAIpC,MAAgB,SAAS,OAAgC;AACvD,MAAI,iBAAiB,WAAW;GAC9B,MAAM,SAAS,MAAM,SAAS;GAC9B,MAAM,OAAO,MAAM,MAAM,SAAS,MAAM,CAAC,aAAa,EAAE,EAAE;GAC1D,MAAM,UAAW,KAAa,SAAU,KAAa;AAErD,OAAI,WAAW,OAAO,WAAW,IAC/B,QAAO,IAAI,oBAAoB,WAAW,wBAAwB;AAEpE,OAAI,WAAW,IACb,QAAO,IAAI,cAAc,WAAW,qBAAqB;AAE3D,OAAI,WAAW,IACb,QAAO,IAAI,gBAAgB,WAAW,kBAAkB;AAE1D,OAAI,WAAW,IACb,QAAO,IAAI,gBAAgB,WAAW,2BAA2B;AAGnE,UAAO,IAAI,aAAa,WAAW,eAAe,SAAS;;AAG7D,MAAI,iBAAiB,MACnB,QAAO,IAAI,aAAa,MAAM,QAAQ;AAGxC,SAAO,IAAI,aAAa,yBAAyB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ConfigurationError } from "../errors/index.js";
|
|
2
|
+
import { JwtAuthManager } from "./manager.js";
|
|
3
|
+
import { Sdk } from "@lumeweb/portal-sdk";
|
|
4
|
+
import { jwtDecode } from "jwt-decode";
|
|
5
|
+
|
|
6
|
+
//#region src/auth/key-exchange.ts
|
|
7
|
+
/**
|
|
8
|
+
* AuthManager that exchanges an API key JWT for a login JWT when needed.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the Go SDK's AuthServiceDefault.GetLoginToken():
|
|
11
|
+
* 1. Decode the JWT audience without verification
|
|
12
|
+
* 2. If aud === JwtPurpose.API, call POST /api/auth/key to exchange for a login JWT
|
|
13
|
+
* 3. Use the login JWT for all subsequent requests
|
|
14
|
+
*
|
|
15
|
+
* The exchange happens lazily on first access (getAuthToken/getAuthHeaders)
|
|
16
|
+
* and is cached for the lifetime of the instance.
|
|
17
|
+
*/
|
|
18
|
+
var KeyExchangeAuthManager = class extends JwtAuthManager {
|
|
19
|
+
resolvedToken = "";
|
|
20
|
+
exchangePromise;
|
|
21
|
+
sdk;
|
|
22
|
+
constructor(jwt, endpoint) {
|
|
23
|
+
super(jwt);
|
|
24
|
+
this.sdk = new Sdk(endpoint);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Decode the JWT audience without verification.
|
|
28
|
+
* Returns undefined if the token can't be decoded or has no audience.
|
|
29
|
+
*/
|
|
30
|
+
getAudience() {
|
|
31
|
+
try {
|
|
32
|
+
const aud = jwtDecode(this.token).aud;
|
|
33
|
+
if (Array.isArray(aud)) return aud[0];
|
|
34
|
+
return typeof aud === "string" ? aud : void 0;
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Exchange the API key JWT for a login JWT if needed.
|
|
41
|
+
* Returns the login JWT, or the original token if no exchange is necessary.
|
|
42
|
+
*/
|
|
43
|
+
resolveToken() {
|
|
44
|
+
if (this.resolvedToken) return Promise.resolve(this.resolvedToken);
|
|
45
|
+
if (this.exchangePromise) return this.exchangePromise;
|
|
46
|
+
if (this.getAudience() !== "api") {
|
|
47
|
+
this.resolvedToken = this.token;
|
|
48
|
+
return Promise.resolve(this.resolvedToken);
|
|
49
|
+
}
|
|
50
|
+
this.exchangePromise = (async () => {
|
|
51
|
+
try {
|
|
52
|
+
const result = await this.sdk.account().loginWithApiKey(this.token);
|
|
53
|
+
if (!result.success || !result.data?.token) throw new ConfigurationError("Failed to exchange API key for login JWT");
|
|
54
|
+
this.resolvedToken = result.data.token;
|
|
55
|
+
return this.resolvedToken;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
this.exchangePromise = void 0;
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
return this.exchangePromise;
|
|
62
|
+
}
|
|
63
|
+
async getAuthToken() {
|
|
64
|
+
return this.resolveToken();
|
|
65
|
+
}
|
|
66
|
+
async getAuthHeaders() {
|
|
67
|
+
return { Authorization: `Bearer ${await this.resolveToken()}` };
|
|
68
|
+
}
|
|
69
|
+
async getAccessToken() {
|
|
70
|
+
return this.resolveToken();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { KeyExchangeAuthManager };
|
|
76
|
+
//# sourceMappingURL=key-exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-exchange.js","names":[],"sources":["../../../src/auth/key-exchange.ts"],"sourcesContent":["import { jwtDecode } from \"jwt-decode\";\nimport { Sdk } from \"@lumeweb/portal-sdk\";\nimport { JwtAuthManager } from \"./manager\";\nimport { JwtPurpose } from \"./types\";\nimport { ConfigurationError } from \"@/errors\";\n\n/**\n * AuthManager that exchanges an API key JWT for a login JWT when needed.\n *\n * Mirrors the Go SDK's AuthServiceDefault.GetLoginToken():\n * 1. Decode the JWT audience without verification\n * 2. If aud === JwtPurpose.API, call POST /api/auth/key to exchange for a login JWT\n * 3. Use the login JWT for all subsequent requests\n *\n * The exchange happens lazily on first access (getAuthToken/getAuthHeaders)\n * and is cached for the lifetime of the instance.\n */\nexport class KeyExchangeAuthManager extends JwtAuthManager {\n private resolvedToken = \"\";\n private exchangePromise?: Promise<string>;\n private readonly sdk: Sdk;\n\n constructor(jwt: string, endpoint: string) {\n super(jwt);\n this.sdk = new Sdk(endpoint);\n }\n\n /**\n * Decode the JWT audience without verification.\n * Returns undefined if the token can't be decoded or has no audience.\n */\n private getAudience(): string | undefined {\n try {\n const decoded = jwtDecode(this.token);\n const aud = decoded.aud;\n if (Array.isArray(aud)) {\n return aud[0];\n }\n return typeof aud === \"string\" ? aud : undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Exchange the API key JWT for a login JWT if needed.\n * Returns the login JWT, or the original token if no exchange is necessary.\n */\n private resolveToken(): Promise<string> {\n if (this.resolvedToken) {\n return Promise.resolve(this.resolvedToken);\n }\n\n if (this.exchangePromise) {\n return this.exchangePromise;\n }\n\n const aud = this.getAudience();\n\n if (aud !== JwtPurpose.API) {\n this.resolvedToken = this.token;\n return Promise.resolve(this.resolvedToken);\n }\n\n this.exchangePromise = (async () => {\n try {\n const result = await this.sdk.account().loginWithApiKey(this.token);\n\n if (!result.success || !result.data?.token) {\n throw new ConfigurationError(\n \"Failed to exchange API key for login JWT\",\n );\n }\n\n this.resolvedToken = result.data.token;\n return this.resolvedToken;\n } catch (err) {\n this.exchangePromise = undefined;\n throw err;\n }\n })();\n\n return this.exchangePromise;\n }\n\n async getAuthToken(): Promise<string> {\n return this.resolveToken();\n }\n\n async getAuthHeaders(): Promise<Record<string, string>> {\n const token = await this.resolveToken();\n return { Authorization: `Bearer ${token}` };\n }\n\n async getAccessToken(): Promise<string> {\n return this.resolveToken();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,IAAa,yBAAb,cAA4C,eAAe;CACzD,AAAQ,gBAAgB;CACxB,AAAQ;CACR,AAAiB;CAEjB,YAAY,KAAa,UAAkB;AACzC,QAAM,IAAI;AACV,OAAK,MAAM,IAAI,IAAI,SAAS;;;;;;CAO9B,AAAQ,cAAkC;AACxC,MAAI;GAEF,MAAM,MADU,UAAU,KAAK,MACZ,CAAC;AACpB,OAAI,MAAM,QAAQ,IAAI,CACpB,QAAO,IAAI;AAEb,UAAO,OAAO,QAAQ,WAAW,MAAM;UACjC;AACN;;;;;;;CAQJ,AAAQ,eAAgC;AACtC,MAAI,KAAK,cACP,QAAO,QAAQ,QAAQ,KAAK,cAAc;AAG5C,MAAI,KAAK,gBACP,QAAO,KAAK;AAKd,MAFY,KAAK,aAEV,YAAqB;AAC1B,QAAK,gBAAgB,KAAK;AAC1B,UAAO,QAAQ,QAAQ,KAAK,cAAc;;AAG5C,OAAK,mBAAmB,YAAY;AAClC,OAAI;IACF,MAAM,SAAS,MAAM,KAAK,IAAI,SAAS,CAAC,gBAAgB,KAAK,MAAM;AAEnE,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,MAAM,MACnC,OAAM,IAAI,mBACR,2CACD;AAGH,SAAK,gBAAgB,OAAO,KAAK;AACjC,WAAO,KAAK;YACL,KAAK;AACZ,SAAK,kBAAkB;AACvB,UAAM;;MAEN;AAEJ,SAAO,KAAK;;CAGd,MAAM,eAAgC;AACpC,SAAO,KAAK,cAAc;;CAG5B,MAAM,iBAAkD;AAEtD,SAAO,EAAE,eAAe,UAAU,MADd,KAAK,cAAc,IACI;;CAG7C,MAAM,iBAAkC;AACtC,SAAO,KAAK,cAAc"}
|
|
@@ -6,23 +6,27 @@
|
|
|
6
6
|
* This is the single source of truth for auth in the Pinner SDK.
|
|
7
7
|
* All clients receive an AuthManager instance and call getAuthHeaders()
|
|
8
8
|
* or getAuthToken() — never touch config.jwt directly.
|
|
9
|
+
*
|
|
10
|
+
* Methods are async to support token exchange (API key → login JWT).
|
|
9
11
|
*/
|
|
10
12
|
interface AuthManager {
|
|
11
13
|
/**
|
|
12
14
|
* Get the raw auth token string.
|
|
15
|
+
* May perform a network call to exchange an API key JWT for a login JWT.
|
|
13
16
|
* Throw ConfigurationError if no token is configured.
|
|
14
17
|
*/
|
|
15
|
-
getAuthToken(): string
|
|
18
|
+
getAuthToken(): Promise<string>;
|
|
16
19
|
/**
|
|
17
20
|
* Get the Authorization header object for use in fetch/ky requests.
|
|
18
21
|
* Example: { Authorization: "Bearer eyJ..." }
|
|
22
|
+
* May perform a network call if token exchange is needed.
|
|
19
23
|
*/
|
|
20
|
-
getAuthHeaders(): Record<string, string
|
|
24
|
+
getAuthHeaders(): Promise<Record<string, string>>;
|
|
21
25
|
/**
|
|
22
26
|
* Get the auth token for use with libraries that expect an accessToken field
|
|
23
27
|
* (e.g. @ipfs-shipyard/pinning-service-client Configuration).
|
|
24
28
|
*/
|
|
25
|
-
getAccessToken(): string
|
|
29
|
+
getAccessToken(): Promise<string>;
|
|
26
30
|
}
|
|
27
31
|
//#endregion
|
|
28
32
|
export { AuthManager };
|
package/dist/esm/auth/manager.js
CHANGED
|
@@ -5,8 +5,7 @@ import { ConfigurationError } from "../errors/index.js";
|
|
|
5
5
|
* Default AuthManager implementation that holds a JWT token.
|
|
6
6
|
*
|
|
7
7
|
* Mirrors the Go SDK's approach: token is set once at construction time
|
|
8
|
-
* and used for all subsequent requests.
|
|
9
|
-
* is needed in the future, it goes here — one place, not scattered across clients.
|
|
8
|
+
* and used for all subsequent requests.
|
|
10
9
|
*/
|
|
11
10
|
var JwtAuthManager = class {
|
|
12
11
|
token;
|
|
@@ -14,13 +13,13 @@ var JwtAuthManager = class {
|
|
|
14
13
|
if (!jwt) throw new ConfigurationError("JWT token is required");
|
|
15
14
|
this.token = jwt;
|
|
16
15
|
}
|
|
17
|
-
getAuthToken() {
|
|
16
|
+
async getAuthToken() {
|
|
18
17
|
return this.token;
|
|
19
18
|
}
|
|
20
|
-
getAuthHeaders() {
|
|
19
|
+
async getAuthHeaders() {
|
|
21
20
|
return { Authorization: `Bearer ${this.token}` };
|
|
22
21
|
}
|
|
23
|
-
getAccessToken() {
|
|
22
|
+
async getAccessToken() {
|
|
24
23
|
return this.token;
|
|
25
24
|
}
|
|
26
25
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/auth/manager.ts"],"sourcesContent":["import { ConfigurationError } from \"@/errors\";\n\n/**\n * Interface for authentication management.\n * Provides auth tokens and headers for API requests.\n *\n * This is the single source of truth for auth in the Pinner SDK.\n * All clients receive an AuthManager instance and call getAuthHeaders()\n * or getAuthToken() — never touch config.jwt directly.\n */\nexport interface AuthManager {\n /**\n * Get the raw auth token string.\n * Throw ConfigurationError if no token is configured.\n */\n getAuthToken(): string
|
|
1
|
+
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/auth/manager.ts"],"sourcesContent":["import { ConfigurationError } from \"@/errors\";\n\n/**\n * Interface for authentication management.\n * Provides auth tokens and headers for API requests.\n *\n * This is the single source of truth for auth in the Pinner SDK.\n * All clients receive an AuthManager instance and call getAuthHeaders()\n * or getAuthToken() — never touch config.jwt directly.\n *\n * Methods are async to support token exchange (API key → login JWT).\n */\nexport interface AuthManager {\n /**\n * Get the raw auth token string.\n * May perform a network call to exchange an API key JWT for a login JWT.\n * Throw ConfigurationError if no token is configured.\n */\n getAuthToken(): Promise<string>;\n\n /**\n * Get the Authorization header object for use in fetch/ky requests.\n * Example: { Authorization: \"Bearer eyJ...\" }\n * May perform a network call if token exchange is needed.\n */\n getAuthHeaders(): Promise<Record<string, string>>;\n\n /**\n * Get the auth token for use with libraries that expect an accessToken field\n * (e.g. @ipfs-shipyard/pinning-service-client Configuration).\n */\n getAccessToken(): Promise<string>;\n}\n\n/**\n * Default AuthManager implementation that holds a JWT token.\n *\n * Mirrors the Go SDK's approach: token is set once at construction time\n * and used for all subsequent requests.\n */\nexport class JwtAuthManager implements AuthManager {\n protected readonly token: string;\n\n constructor(jwt: string) {\n if (!jwt) {\n throw new ConfigurationError(\"JWT token is required\");\n }\n this.token = jwt;\n }\n\n async getAuthToken(): Promise<string> {\n return this.token;\n }\n\n async getAuthHeaders(): Promise<Record<string, string>> {\n return { Authorization: `Bearer ${this.token}` };\n }\n\n async getAccessToken(): Promise<string> {\n return this.token;\n }\n}\n"],"mappings":";;;;;;;;;AAwCA,IAAa,iBAAb,MAAmD;CACjD,AAAmB;CAEnB,YAAY,KAAa;AACvB,MAAI,CAAC,IACH,OAAM,IAAI,mBAAmB,wBAAwB;AAEvD,OAAK,QAAQ;;CAGf,MAAM,eAAgC;AACpC,SAAO,KAAK;;CAGd,MAAM,iBAAkD;AACtD,SAAO,EAAE,eAAe,UAAU,KAAK,SAAS;;CAGlD,MAAM,iBAAkC;AACtC,SAAO,KAAK"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DEFAULT_ENDPOINT, DEFAULT_GATEWAY } from "./types/constants.js";
|
|
2
|
+
|
|
3
|
+
//#region src/config.ts
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
endpoint: DEFAULT_ENDPOINT,
|
|
6
|
+
gateway: DEFAULT_GATEWAY,
|
|
7
|
+
timeout: 12e4,
|
|
8
|
+
retries: 3
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { DEFAULT_CONFIG };
|
|
13
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../../src/config.ts"],"sourcesContent":["import { DEFAULT_ENDPOINT, DEFAULT_GATEWAY } from \"./types/constants\";\nimport type { Storage } from \"unstorage\";\nimport type { Datastore } from \"interface-datastore\";\n\nexport interface PinnerConfig {\n /**\n * JWT authentication token. Required for all API operations.\n */\n jwt: string;\n\n /**\n * API endpoint URL. Defaults to the official pinning service.\n * @default \"https://ipfs.pinner.xyz\"\n */\n endpoint?: string;\n\n /**\n * IPFS gateway URL for content retrieval.\n * @default \"https://dweb.link\"\n */\n gateway?: string;\n\n /**\n * Allowed MIME types for upload. If undefined, all types allowed.\n */\n allowedFileTypes?: string[];\n\n /**\n * Custom fetch implementation.\n */\n fetch?: typeof fetch;\n\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 storage instance for both Helia blockstore and datastore.\n * If provided, this storage will be used instead of creating default storage.\n * The storage instance must implement the unstorage Storage interface.\n * Used when datastore is not provided.\n */\n storage?: Storage;\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 neither datastore nor storage are provided.\n * @default \"pinner-helia-data\"\n */\n datastoreName?: string;\n\n /**\n * Upload request timeout in milliseconds.\n * Applied to XHR uploads. TUS does not expose a timeout option.\n * @default 120_000\n */\n timeout?: number;\n\n /**\n * Number of retry attempts for failed uploads.\n * Applied to XHR uploads (TUS uses retryDelays instead).\n * @default 3\n */\n retries?: number;\n}\n\nexport const DEFAULT_CONFIG: Partial<PinnerConfig> = {\n endpoint: DEFAULT_ENDPOINT,\n gateway: DEFAULT_GATEWAY,\n timeout: 120_000,\n retries: 3,\n};\n"],"mappings":";;;AAsEA,MAAa,iBAAwC;CACnD,UAAU;CACV,SAAS;CACT,SAAS;CACT,SAAS;CACV"}
|
package/dist/esm/pin/client.js
CHANGED
|
@@ -11,17 +11,17 @@ var PinClient = class {
|
|
|
11
11
|
this.config = config;
|
|
12
12
|
this.auth = auth;
|
|
13
13
|
}
|
|
14
|
-
getClient() {
|
|
14
|
+
async getClient() {
|
|
15
15
|
if (this.client) return this.client;
|
|
16
16
|
this.client = new RemotePinningServiceClient(new Configuration({
|
|
17
17
|
endpointUrl: this.config.endpoint,
|
|
18
|
-
accessToken: this.auth.getAccessToken(),
|
|
18
|
+
accessToken: await this.auth.getAccessToken(),
|
|
19
19
|
fetchApi: this.config.fetch ?? fetch
|
|
20
20
|
}));
|
|
21
21
|
return this.client;
|
|
22
22
|
}
|
|
23
23
|
async *add(cid, options) {
|
|
24
|
-
const client = this.getClient();
|
|
24
|
+
const client = await this.getClient();
|
|
25
25
|
const pin = {
|
|
26
26
|
cid: cid.toString(),
|
|
27
27
|
name: options?.name,
|
|
@@ -32,7 +32,7 @@ var PinClient = class {
|
|
|
32
32
|
yield cid;
|
|
33
33
|
}
|
|
34
34
|
async *ls(options) {
|
|
35
|
-
const response = await this.getClient().pinsGet(this.normalizeListOptions(options), { signal: options?.signal });
|
|
35
|
+
const response = await (await this.getClient()).pinsGet(this.normalizeListOptions(options), { signal: options?.signal });
|
|
36
36
|
for (const result of response.results) yield this.mapResponse(result);
|
|
37
37
|
}
|
|
38
38
|
async isPinned(cid, options) {
|
|
@@ -44,12 +44,12 @@ var PinClient = class {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
async get(cid, options) {
|
|
47
|
-
const response = await this.getClient().pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
47
|
+
const response = await (await this.getClient()).pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
48
48
|
if (response.results.length === 0) throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);
|
|
49
49
|
return this.mapResponse(response.results[0]);
|
|
50
50
|
}
|
|
51
51
|
async setMetadata(cid, metadata, options) {
|
|
52
|
-
const client = this.getClient();
|
|
52
|
+
const client = await this.getClient();
|
|
53
53
|
const response = await client.pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
54
54
|
if (response.results.length === 0) throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);
|
|
55
55
|
const pin = response.results[0];
|
|
@@ -64,14 +64,14 @@ var PinClient = class {
|
|
|
64
64
|
}, { signal: options?.signal });
|
|
65
65
|
}
|
|
66
66
|
async *rm(cid, options) {
|
|
67
|
-
const response = await this.getClient().pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
67
|
+
const response = await (await this.getClient()).pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
68
68
|
await Promise.all([...response.results].map(async (result) => {
|
|
69
69
|
return this.rmByRequestId(result.requestid, options);
|
|
70
70
|
}));
|
|
71
71
|
yield cid;
|
|
72
72
|
}
|
|
73
73
|
async rmByRequestId(requestId, options) {
|
|
74
|
-
await this.getClient().pinsRequestidDelete({ requestid: requestId }, { signal: options?.signal });
|
|
74
|
+
await (await this.getClient()).pinsRequestidDelete({ requestid: requestId }, { signal: options?.signal });
|
|
75
75
|
}
|
|
76
76
|
mapResponse(response) {
|
|
77
77
|
return {
|
|
@@ -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 { AuthManager } from \"@/auth\";\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 private readonly auth: AuthManager;\n\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config;\n this.auth = auth;\n }\n\n protected getClient(): RemotePinningServiceClient {\n if (this.client) {\n return this.client;\n }\n\n this.client = new RemotePinningServiceClient(\n new Configuration({\n endpointUrl: this.config.endpoint,\n accessToken: this.auth.getAccessToken(),\n fetchApi: this.config.fetch ?? fetch,\n }),\n );\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":";;;;;AAkBA,IAAa,YAAb,MAA6C;CAC3C,AAAQ,SAA4C;CACpD,AAAQ;CACR,AAAiB;CAEjB,YAAY,QAAsB,MAAmB;AACnD,OAAK,SAAS;AACd,OAAK,OAAO;;CAGd,
|
|
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 { AuthManager } from \"@/auth\";\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 private readonly auth: AuthManager;\n\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config;\n this.auth = auth;\n }\n\n protected async getClient(): Promise<RemotePinningServiceClient> {\n if (this.client) {\n return this.client;\n }\n\n this.client = new RemotePinningServiceClient(\n new Configuration({\n endpointUrl: this.config.endpoint,\n accessToken: await this.auth.getAccessToken(),\n fetchApi: this.config.fetch ?? fetch,\n }),\n );\n return this.client;\n }\n\n async *add(\n cid: CID,\n options?: RemoteAddOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = await 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 = await 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 = await 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 = await 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 = await 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 = await 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.js
CHANGED
|
@@ -5,7 +5,9 @@ import { PinClient } from "./pin/client.js";
|
|
|
5
5
|
import "./pin/index.js";
|
|
6
6
|
import { IpnsClient } from "./api/ipns.js";
|
|
7
7
|
import { WebsitesClient } from "./api/websites.js";
|
|
8
|
-
import {
|
|
8
|
+
import { KeyExchangeAuthManager } from "./auth/key-exchange.js";
|
|
9
|
+
import "./auth/index.js";
|
|
10
|
+
import { DEFAULT_CONFIG } from "./config.js";
|
|
9
11
|
import { CID } from "multiformats/cid";
|
|
10
12
|
|
|
11
13
|
//#region src/pinner.ts
|
|
@@ -21,7 +23,8 @@ var Pinner = class {
|
|
|
21
23
|
* @param config SDK configuration object
|
|
22
24
|
*/
|
|
23
25
|
constructor(config) {
|
|
24
|
-
|
|
26
|
+
const endpoint = config.endpoint ?? DEFAULT_CONFIG.endpoint;
|
|
27
|
+
this.auth = new KeyExchangeAuthManager(config.jwt, endpoint);
|
|
25
28
|
this.uploadManager = new UploadManager(config, this.auth);
|
|
26
29
|
this._pins = new PinClient(config, this.auth);
|
|
27
30
|
this._ipns = new IpnsClient(config, this.auth);
|
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 { IpnsClient } from \"./api/ipns\";\nimport { WebsitesClient } from \"./api/websites\";\nimport { JwtAuthManager, type AuthManager } from \"@/auth\";\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 private readonly auth: AuthManager;\n\n /**\n * Create a new Pinner SDK instance.\n * @param config SDK configuration object\n */\n constructor(config: PinnerConfig) {\n this.auth = new
|
|
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 { JwtAuthManager, KeyExchangeAuthManager, type AuthManager } from \"@/auth\";\nimport { DEFAULT_CONFIG } from \"./config\";\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 private readonly auth: AuthManager;\n\n /**\n * Create a new Pinner SDK instance.\n * @param config SDK configuration object\n */\n constructor(config: PinnerConfig) {\n const endpoint = config.endpoint ?? DEFAULT_CONFIG.endpoint!;\n this.auth = new KeyExchangeAuthManager(config.jwt, endpoint);\n this.uploadManager = new UploadManager(config, this.auth);\n this._pins = new PinClient(config, this.auth);\n this._ipns = new IpnsClient(config, this.auth);\n this._websites = new WebsitesClient(config, this.auth);\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 * @param file The file to upload\n * @param options Upload configuration\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 * @param files Array of files to upload as a directory\n * @param options Upload configuration\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 * @param file CAR file or stream to upload\n * @param options Upload configuration\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 * @param cid CID of content to pin (string or CID object)\n * @param options Remote add options\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 * @param options List filtering options\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 * @param cid CID of the pinned content to check\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 * @param cid CID to check\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 * @param cid CID of the pin\n * @param metadata Key-value metadata to set\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 * @param cid CID to unpin\n * @param options Abort options\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 * @param requestId The request ID to remove\n * @param options Abort options\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":";;;;;;;;;;;;;AAwBA,IAAa,SAAb,MAAoB;CAClB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAiB;;;;;CAMjB,YAAY,QAAsB;EAChC,MAAM,WAAW,OAAO,YAAY,eAAe;AACnD,OAAK,OAAO,IAAI,uBAAuB,OAAO,KAAK,SAAS;AAC5D,OAAK,gBAAgB,IAAI,cAAc,QAAQ,KAAK,KAAK;AACzD,OAAK,QAAQ,IAAI,UAAU,QAAQ,KAAK,KAAK;AAC7C,OAAK,QAAQ,IAAI,WAAW,QAAQ,KAAK,KAAK;AAC9C,OAAK,YAAY,IAAI,eAAe,QAAQ,KAAK,KAAK;;;;;CAMxD,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;;;;;;;;CASd,MAAM,cACJ,MACA,SACuB;AAEvB,UAAO,MADiB,KAAK,OAAO,MAAM,QAAQ,EACjC;;;;;;;;CASnB,MAAM,iBACJ,OACA,SACuB;AACvB,SAAO,KAAK,cAAc,iBAAiB,OAAO,QAAQ;;;;;;;CAQ5D,MAAM,gBACJ,OACA,SAC0B;AAC1B,SAAO,KAAK,cAAc,gBAAgB,OAAO,QAAQ;;;;;;;;CAS3D,MAAM,UACJ,MACA,SAC0B;AAC1B,SAAO,KAAK,cAAc,UAAU,MAAM,QAAQ;;;;;;;CAQpD,MAAM,UACJ,KACA,SAC+C;EAC/C,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,IAAI,QAAQ,QAAQ;;;;;;CAOvC,MAAM,SAAS,SAAiD;EAC9D,MAAM,OAAoB,EAAE;AAC5B,aAAW,MAAM,OAAO,KAAK,KAAK,GAAG,QAAQ,CAC3C,MAAK,KAAK,IAAI;AAEhB,SAAO;;;;;;CAOT,MAAM,aAAa,KAAuC;EACxD,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,IAAI,OAAO;;;;;;CAO9B,MAAM,SAAS,KAAqC;EAClD,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,SAAS,OAAO;;;;;;;CAQnC,MAAM,eACJ,KACA,UACe;EACf,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,YAAY,QAAQ,SAAS;;;;;;;CAQhD,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;;;;;;;CAUxB,MAAM,iBAAiB,WAAmB,SAAuC;AAC/E,SAAO,KAAK,KAAK,cAAc,WAAW,QAAQ;;;;;CAMpD,UAAgB;AACd,OAAK,cAAc,SAAS"}
|
|
@@ -17,12 +17,12 @@ var BaseUploadHandler = class {
|
|
|
17
17
|
async upload(input, options) {
|
|
18
18
|
const normalized = normalizeUploadInput(input, options);
|
|
19
19
|
const uppy = new Uppy();
|
|
20
|
-
const { fileId, resultPromise, progress } = this.#setupUppyHandlers(uppy, normalized, options);
|
|
20
|
+
const { fileId, resultPromise, progress } = await this.#setupUppyHandlers(uppy, normalized, options);
|
|
21
21
|
await this.#addFileToUppy(uppy, normalized, normalized.size);
|
|
22
22
|
this.#startUpload(uppy, options);
|
|
23
23
|
return this.#createUploadOperation(uppy, fileId, resultPromise, progress);
|
|
24
24
|
}
|
|
25
|
-
#setupUppyHandlers(uppy, normalized, options) {
|
|
25
|
+
async #setupUppyHandlers(uppy, normalized, options) {
|
|
26
26
|
let fileId = null;
|
|
27
27
|
let hasRejected = false;
|
|
28
28
|
const progress = {
|
|
@@ -37,7 +37,7 @@ var BaseUploadHandler = class {
|
|
|
37
37
|
options?.onError?.(error);
|
|
38
38
|
rejectResult(error);
|
|
39
39
|
};
|
|
40
|
-
this.configurePlugin(uppy);
|
|
40
|
+
await this.configurePlugin(uppy);
|
|
41
41
|
uppy.on("progress", (progressBytes) => {
|
|
42
42
|
progress.bytesUploaded = progressBytes;
|
|
43
43
|
progress.percentage = progressBytes / progress.bytesTotal * 100;
|
|
@@ -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 { AuthManager } from \"@/auth\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadProgress,\n UploadResult,\n} from \"@/types/upload\";\nimport { UploadResultSymbol } from \"@/types/upload\";\nimport type { PostUploadResponse } from \"@/api/generated/schemas\";\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 protected readonly auth: AuthManager;\n\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config as Required<PinnerConfig>;\n this.auth = auth;\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 /**\n * List of file manager items\n */\n data: File | ReadableStream<Uint8Array>;\n /**\n * Name for the pin or filter\n */\n name: string;\n /**\n * MIME type or content type\n */\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 protected mapUploadResponse(\n body: unknown,\n uploadId: string,\n overrides?: Partial<UploadResult>,\n ): UploadResult {\n if (body && typeof body === \"object\" && \"CID\" in body) {\n const { CID } = body as PostUploadResponse;\n return {\n id: uploadId,\n cid: CID,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n\n return {\n id: uploadId,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\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 { AuthManager } from \"@/auth\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadProgress,\n UploadResult,\n} from \"@/types/upload\";\nimport { UploadResultSymbol } from \"@/types/upload\";\nimport type { PostUploadResponse } from \"@/api/generated/schemas\";\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 protected readonly auth: AuthManager;\n\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config as Required<PinnerConfig>;\n this.auth = auth;\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 } = await 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 async #setupUppyHandlers(\n uppy: Uppy,\n normalized: { size: number },\n options?: UploadOptions,\n ): Promise<{\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 await 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 /**\n * List of file manager items\n */\n data: File | ReadableStream<Uint8Array>;\n /**\n * Name for the pin or filter\n */\n name: string;\n /**\n * MIME type or content type\n */\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 | Promise<void>;\n protected abstract parseResult(result: unknown): UploadResult;\n protected abstract getUploadSource(): string;\n\n protected mapUploadResponse(\n body: unknown,\n uploadId: string,\n overrides?: Partial<UploadResult>,\n ): UploadResult {\n if (body && typeof body === \"object\" && \"CID\" in body) {\n const { CID } = body as PostUploadResponse;\n return {\n id: uploadId,\n cid: CID,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n\n return {\n id: uploadId,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ var UploadManager = class {
|
|
|
19
19
|
xhrHandler;
|
|
20
20
|
tusHandler;
|
|
21
21
|
portalSdk;
|
|
22
|
+
portalSdkReady;
|
|
22
23
|
uploadLimit = TUS_SIZE_THRESHOLD;
|
|
23
24
|
limitFetched = false;
|
|
24
25
|
/**
|
|
@@ -32,18 +33,29 @@ var UploadManager = class {
|
|
|
32
33
|
this.xhrHandler = new XHRUploadHandler(config, auth);
|
|
33
34
|
this.tusHandler = new TUSUploadHandler(config, auth);
|
|
34
35
|
this.portalSdk = new Sdk(config.endpoint || "https://ipfs.pinner.xyz");
|
|
35
|
-
this.portalSdk.setAuthToken(auth.getAuthToken());
|
|
36
36
|
configureCar({
|
|
37
37
|
datastoreName: config.datastoreName,
|
|
38
38
|
datastore: config.datastore
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
+
async #initPortalSdk() {
|
|
42
|
+
const token = await this.auth.getAuthToken();
|
|
43
|
+
this.portalSdk.setAuthToken(token);
|
|
44
|
+
}
|
|
45
|
+
async #ensurePortalSdkReady() {
|
|
46
|
+
if (!this.portalSdkReady) this.portalSdkReady = this.#initPortalSdk().catch((err) => {
|
|
47
|
+
this.portalSdkReady = void 0;
|
|
48
|
+
throw err;
|
|
49
|
+
});
|
|
50
|
+
return this.portalSdkReady;
|
|
51
|
+
}
|
|
41
52
|
/**
|
|
42
53
|
* Fetch the upload size limit from the API. Falls back to 100 MB on failure.
|
|
43
54
|
*/
|
|
44
55
|
async fetchUploadLimit() {
|
|
45
56
|
if (this.limitFetched) return this.uploadLimit;
|
|
46
57
|
try {
|
|
58
|
+
await this.#ensurePortalSdkReady();
|
|
47
59
|
const result = await this.portalSdk.account().uploadLimit();
|
|
48
60
|
if (result.success && result.data?.limit) this.uploadLimit = result.data.limit;
|
|
49
61
|
} catch {
|
|
@@ -88,6 +100,7 @@ var UploadManager = class {
|
|
|
88
100
|
* @returns UploadResult with operation status merged in
|
|
89
101
|
*/
|
|
90
102
|
async waitForOperation(input, options) {
|
|
103
|
+
await this.#ensurePortalSdkReady();
|
|
91
104
|
if (isUploadResult(input) && input.operationId) return await this.#waitForOperationById(input, options);
|
|
92
105
|
if (isUploadResult(input) && input.cid) return await this.#waitForOperationByCid(input, options);
|
|
93
106
|
if (isUploadResult(input) && input.id) {
|
|
@@ -127,6 +140,7 @@ var UploadManager = class {
|
|
|
127
140
|
* @returns UploadResult with operation status merged in
|
|
128
141
|
*/
|
|
129
142
|
async #waitForOperationByCid(uploadResult, options) {
|
|
143
|
+
await this.#ensurePortalSdkReady();
|
|
130
144
|
if (!uploadResult.cid) throw new Error("Cannot wait for operation by CID — CID not available. Use upload result ID to poll GET /api/upload/result/{id} instead.");
|
|
131
145
|
const params = {
|
|
132
146
|
filters: [createEqFilter("cid", uploadResult.cid)],
|
|
@@ -167,7 +181,7 @@ var UploadManager = class {
|
|
|
167
181
|
const uploadId = uploadResult.id;
|
|
168
182
|
if (!uploadId) throw new Error("No upload ID available to poll for upload result");
|
|
169
183
|
const fetchUrl = `${this.config.endpoint || "https://ipfs.pinner.xyz"}/api/upload/result/${encodeURIComponent(uploadId)}`;
|
|
170
|
-
const headers = this.auth.getAuthHeaders();
|
|
184
|
+
const headers = await this.auth.getAuthHeaders();
|
|
171
185
|
const result = await poll(async () => {
|
|
172
186
|
const response = await fetch(fetchUrl, { headers });
|
|
173
187
|
if (response.status === 404) return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":["#validateInput","#uploadInput","#uploadCarFile","#waitForOperationById","#waitForOperationByCid","#waitForUploadResult","#uploadCarResult","#isCarFileUpload","#uploadFile"],"sources":["../../../src/upload/manager.ts"],"sourcesContent":["import type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport {\n AccountError,\n OPERATION_STATUS,\n type OperationsQueryParams,\n Sdk,\n poll,\n} from \"@lumeweb/portal-sdk\";\nimport { createEqFilter } from \"@lumeweb/query-builder\";\nimport type { UploadResultResponse } from \"@/api/generated/schemas\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type { AuthManager } from \"@/auth\";\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\n/**\n * Handles file uploads via XHR or TUS protocol based on file size.\n */\nexport class UploadManager {\n private config: PinnerConfig;\n private readonly auth: AuthManager;\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 /**\n * Create a new UploadManager.\n * @param config SDK configuration\n * @param auth AuthManager for authentication\n */\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config;\n this.auth = auth;\n this.xhrHandler = new XHRUploadHandler(config, auth);\n this.tusHandler = new TUSUploadHandler(config, auth);\n this.portalSdk = new Sdk(config.endpoint || DEFAULT_ENDPOINT);\n this.portalSdk.setAuthToken(auth.getAuthToken());\n configureCar({\n datastoreName: config.datastoreName,\n datastore: config.datastore,\n });\n }\n\n /**\n * Fetch the upload size limit from the API. Falls back to 100 MB on failure.\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 /**\n * Get the cached upload size limit (in bytes). Call fetchUploadLimit() first.\n */\n getUploadLimit(): number {\n return this.uploadLimit;\n }\n\n /**\n * Upload a file or stream to IPFS.\n * @param input File, stream, or upload object\n * @param options Upload configuration\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 /**\n * Upload a CAR file or stream directly without preprocessing.\n * @param input CAR file or stream\n * @param options Upload configuration\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 return await this.#waitForOperationById(input, options);\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: UploadResult with no CID and no operationId — TUS upload.\n // Poll GET /api/upload/result/{id} to resolve the CID, then proceed via CID.\n if (isUploadResult(input) && input.id) {\n const resolved = await this.#waitForUploadResult(input, options);\n if (resolved.cid) {\n return await this.#waitForOperationByCid(resolved, options);\n }\n return resolved;\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 if (!uploadResult.cid) {\n throw new Error(\"Cannot wait for operation by CID — CID not available. Use upload result ID to poll GET /api/upload/result/{id} instead.\");\n }\n\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 async #waitForOperationById(\n input: UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\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 return {\n ...input,\n cid: operation.cid,\n operationId: operation.id,\n };\n }\n\n async #waitForUploadResult(\n uploadResult: UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n const uploadId = uploadResult.id;\n if (!uploadId) {\n throw new Error(\"No upload ID available to poll for upload result\");\n }\n\n const baseUrl = this.config.endpoint || DEFAULT_ENDPOINT;\n const fetchUrl = `${baseUrl}/api/upload/result/${encodeURIComponent(uploadId)}`;\n const headers = this.auth.getAuthHeaders();\n\n const result = await poll<UploadResultResponse>(\n async () => {\n const response = await fetch(fetchUrl, { headers });\n\n if (response.status === 404) {\n return {\n success: false,\n error: new AccountError(`Upload result not found for ID ${uploadId}`, 404),\n };\n }\n\n const body: UploadResultResponse = await response.json();\n\n if (body.status === \"failed\") {\n return {\n success: false,\n error: new AccountError(body.error || `Upload failed for ID ${uploadId}`, 500),\n };\n }\n\n return { success: true, data: body };\n },\n (data) => data.status === \"completed\" || data.status === \"duplicate\",\n { interval: options?.interval, timeout: options?.timeout },\n );\n\n if (!result.success) {\n throw result.error;\n }\n\n return {\n ...uploadResult,\n ...(result.data.cid ? { cid: result.data.cid } : {}),\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 /**\n * Upload an array of files as an IPFS directory.\n * @param files Files to upload\n * @param options Upload configuration\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":["#initPortalSdk","#ensurePortalSdkReady","#validateInput","#uploadInput","#uploadCarFile","#waitForOperationById","#waitForOperationByCid","#waitForUploadResult","#uploadCarResult","#isCarFileUpload","#uploadFile"],"sources":["../../../src/upload/manager.ts"],"sourcesContent":["import type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport {\n AccountError,\n OPERATION_STATUS,\n type OperationsQueryParams,\n Sdk,\n poll,\n} from \"@lumeweb/portal-sdk\";\nimport { createEqFilter } from \"@lumeweb/query-builder\";\nimport type { UploadResultResponse } from \"@/api/generated/schemas\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type { AuthManager } from \"@/auth\";\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\n/**\n * Handles file uploads via XHR or TUS protocol based on file size.\n */\nexport class UploadManager {\n private config: PinnerConfig;\n private readonly auth: AuthManager;\n private xhrHandler: XHRUploadHandler;\n private tusHandler: TUSUploadHandler;\n private portalSdk: Sdk;\n private portalSdkReady?: Promise<void>;\n private uploadLimit: number = TUS_SIZE_THRESHOLD; // Default to 100 MB\n private limitFetched: boolean = false;\n\n /**\n * Create a new UploadManager.\n * @param config SDK configuration\n * @param auth AuthManager for authentication\n */\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config;\n this.auth = auth;\n this.xhrHandler = new XHRUploadHandler(config, auth);\n this.tusHandler = new TUSUploadHandler(config, auth);\n this.portalSdk = new Sdk(config.endpoint || DEFAULT_ENDPOINT);\n configureCar({\n datastoreName: config.datastoreName,\n datastore: config.datastore,\n });\n }\n\n async #initPortalSdk(): Promise<void> {\n const token = await this.auth.getAuthToken();\n this.portalSdk.setAuthToken(token);\n }\n\n async #ensurePortalSdkReady(): Promise<void> {\n if (!this.portalSdkReady) {\n this.portalSdkReady = this.#initPortalSdk().catch((err) => {\n this.portalSdkReady = undefined;\n throw err;\n });\n }\n return this.portalSdkReady;\n }\n\n /**\n * Fetch the upload size limit from the API. Falls back to 100 MB on failure.\n */\n async fetchUploadLimit(): Promise<number> {\n if (this.limitFetched) {\n return this.uploadLimit;\n }\n\n try {\n await this.#ensurePortalSdkReady();\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 /**\n * Get the cached upload size limit (in bytes). Call fetchUploadLimit() first.\n */\n getUploadLimit(): number {\n return this.uploadLimit;\n }\n\n /**\n * Upload a file or stream to IPFS.\n * @param input File, stream, or upload object\n * @param options Upload configuration\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 /**\n * Upload a CAR file or stream directly without preprocessing.\n * @param input CAR file or stream\n * @param options Upload configuration\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 await this.#ensurePortalSdkReady();\n // Scenario 1: UploadResult with operationId - use it directly\n if (isUploadResult(input) && input.operationId) {\n return await this.#waitForOperationById(input, options);\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: UploadResult with no CID and no operationId — TUS upload.\n // Poll GET /api/upload/result/{id} to resolve the CID, then proceed via CID.\n if (isUploadResult(input) && input.id) {\n const resolved = await this.#waitForUploadResult(input, options);\n if (resolved.cid) {\n return await this.#waitForOperationByCid(resolved, options);\n }\n return resolved;\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 await this.#ensurePortalSdkReady();\n if (!uploadResult.cid) {\n throw new Error(\"Cannot wait for operation by CID — CID not available. Use upload result ID to poll GET /api/upload/result/{id} instead.\");\n }\n\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 async #waitForOperationById(\n input: UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\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 return {\n ...input,\n cid: operation.cid,\n operationId: operation.id,\n };\n }\n\n async #waitForUploadResult(\n uploadResult: UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n const uploadId = uploadResult.id;\n if (!uploadId) {\n throw new Error(\"No upload ID available to poll for upload result\");\n }\n\n const baseUrl = this.config.endpoint || DEFAULT_ENDPOINT;\n const fetchUrl = `${baseUrl}/api/upload/result/${encodeURIComponent(uploadId)}`;\n const headers = await this.auth.getAuthHeaders();\n\n const result = await poll<UploadResultResponse>(\n async () => {\n const response = await fetch(fetchUrl, { headers });\n\n if (response.status === 404) {\n return {\n success: false,\n error: new AccountError(`Upload result not found for ID ${uploadId}`, 404),\n };\n }\n\n const body: UploadResultResponse = await response.json();\n\n if (body.status === \"failed\") {\n return {\n success: false,\n error: new AccountError(body.error || `Upload failed for ID ${uploadId}`, 500),\n };\n }\n\n return { success: true, data: body };\n },\n (data) => data.status === \"completed\" || data.status === \"duplicate\",\n { interval: options?.interval, timeout: options?.timeout },\n );\n\n if (!result.success) {\n throw result.error;\n }\n\n return {\n ...uploadResult,\n ...(result.data.cid ? { cid: result.data.cid } : {}),\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 /**\n * Upload an array of files as an IPFS directory.\n * @param files Files to upload\n * @param options Upload configuration\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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"}
|
|
@@ -7,10 +7,10 @@ var TUSUploadHandler = class extends BaseUploadHandler {
|
|
|
7
7
|
constructor(config, auth) {
|
|
8
8
|
super(config, auth);
|
|
9
9
|
}
|
|
10
|
-
configurePlugin(uppy) {
|
|
10
|
+
async configurePlugin(uppy) {
|
|
11
11
|
uppy.use(TusPlugin, {
|
|
12
12
|
endpoint: `${this.config.endpoint}/api/upload/tus`,
|
|
13
|
-
headers: this.auth.getAuthHeaders(),
|
|
13
|
+
headers: await this.auth.getAuthHeaders(),
|
|
14
14
|
chunkSize: 10 * 1024 * 1024,
|
|
15
15
|
retryDelays: [
|
|
16
16
|
0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tus-upload.js","names":[],"sources":["../../../src/upload/tus-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport TusPlugin from \"@uppy/tus\";\nimport type { UploadResult } from \"@/types/upload\";\nimport { BaseUploadHandler } from \"./base-upload\";\nimport type { PinnerConfig } from \"@/config\";\nimport type { AuthManager } from \"@/auth\";\nimport { UPLOAD_SOURCE_TUS } from \"./constants\";\n\nexport class TUSUploadHandler extends BaseUploadHandler {\n constructor(config: PinnerConfig, auth: AuthManager) {\n super(config, auth);\n }\n protected configurePlugin(uppy: Uppy): void {\n uppy.use(TusPlugin, {\n endpoint: `${this.config.endpoint}/api/upload/tus`,\n headers: this.auth.getAuthHeaders(),\n chunkSize: 10 * 1024 * 1024,\n retryDelays: [0, 1000, 3000, 5000],\n });\n }\n\n protected parseResult(result: unknown): UploadResult {\n const uppyResponse = result as\n | {\n uploadURL: string;\n body?: unknown;\n }\n | undefined;\n\n if (!uppyResponse) {\n return this.mapUploadResponse(undefined, \"\");\n }\n\n const uploadId = uppyResponse.uploadURL?.split(\"/\").pop() || \"\";\n const body = uppyResponse.body;\n\n return this.mapUploadResponse(body, uploadId, {\n isDirectory: false,\n });\n }\n\n protected getUploadSource(): string {\n return UPLOAD_SOURCE_TUS;\n }\n}\n"],"mappings":";;;;;AAQA,IAAa,mBAAb,cAAsC,kBAAkB;CACtD,YAAY,QAAsB,MAAmB;AACnD,QAAM,QAAQ,KAAK;;CAErB,
|
|
1
|
+
{"version":3,"file":"tus-upload.js","names":[],"sources":["../../../src/upload/tus-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport TusPlugin from \"@uppy/tus\";\nimport type { UploadResult } from \"@/types/upload\";\nimport { BaseUploadHandler } from \"./base-upload\";\nimport type { PinnerConfig } from \"@/config\";\nimport type { AuthManager } from \"@/auth\";\nimport { UPLOAD_SOURCE_TUS } from \"./constants\";\n\nexport class TUSUploadHandler extends BaseUploadHandler {\n constructor(config: PinnerConfig, auth: AuthManager) {\n super(config, auth);\n }\n protected async configurePlugin(uppy: Uppy): Promise<void> {\n uppy.use(TusPlugin, {\n endpoint: `${this.config.endpoint}/api/upload/tus`,\n headers: await this.auth.getAuthHeaders(),\n chunkSize: 10 * 1024 * 1024,\n retryDelays: [0, 1000, 3000, 5000],\n });\n }\n\n protected parseResult(result: unknown): UploadResult {\n const uppyResponse = result as\n | {\n uploadURL: string;\n body?: unknown;\n }\n | undefined;\n\n if (!uppyResponse) {\n return this.mapUploadResponse(undefined, \"\");\n }\n\n const uploadId = uppyResponse.uploadURL?.split(\"/\").pop() || \"\";\n const body = uppyResponse.body;\n\n return this.mapUploadResponse(body, uploadId, {\n isDirectory: false,\n });\n }\n\n protected getUploadSource(): string {\n return UPLOAD_SOURCE_TUS;\n }\n}\n"],"mappings":";;;;;AAQA,IAAa,mBAAb,cAAsC,kBAAkB;CACtD,YAAY,QAAsB,MAAmB;AACnD,QAAM,QAAQ,KAAK;;CAErB,MAAgB,gBAAgB,MAA2B;AACzD,OAAK,IAAI,WAAW;GAClB,UAAU,GAAG,KAAK,OAAO,SAAS;GAClC,SAAS,MAAM,KAAK,KAAK,gBAAgB;GACzC,WAAW,KAAK,OAAO;GACvB,aAAa;IAAC;IAAG;IAAM;IAAM;IAAK;GACnC,CAAC;;CAGJ,AAAU,YAAY,QAA+B;EACnD,MAAM,eAAe;AAOrB,MAAI,CAAC,aACH,QAAO,KAAK,kBAAkB,QAAW,GAAG;EAG9C,MAAM,WAAW,aAAa,WAAW,MAAM,IAAI,CAAC,KAAK,IAAI;EAC7D,MAAM,OAAO,aAAa;AAE1B,SAAO,KAAK,kBAAkB,MAAM,UAAU,EAC5C,aAAa,OACd,CAAC;;CAGJ,AAAU,kBAA0B;AAClC,SAAO"}
|
|
@@ -4,12 +4,12 @@ import XHRUpload from "@lumeweb/uppy-post-upload";
|
|
|
4
4
|
|
|
5
5
|
//#region src/upload/xhr-upload.ts
|
|
6
6
|
var XHRUploadHandler = class extends BaseUploadHandler {
|
|
7
|
-
configurePlugin(uppy) {
|
|
7
|
+
async configurePlugin(uppy) {
|
|
8
8
|
uppy.use(XHRUpload, {
|
|
9
9
|
endpoint: `${this.config.endpoint}/api/upload`,
|
|
10
10
|
fieldName: "file",
|
|
11
11
|
formData: true,
|
|
12
|
-
headers: this.auth.getAuthHeaders(),
|
|
12
|
+
headers: await this.auth.getAuthHeaders(),
|
|
13
13
|
timeout: this.config.timeout,
|
|
14
14
|
retries: this.config.retries
|
|
15
15
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xhr-upload.js","names":[],"sources":["../../../src/upload/xhr-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport XHRUpload from \"@lumeweb/uppy-post-upload\";\nimport type { UploadResult } from \"@/types/upload\";\nimport { BaseUploadHandler } from \"./base-upload\";\nimport { UPLOAD_SOURCE_XHR } from \"./constants\";\n\nexport class XHRUploadHandler extends BaseUploadHandler {\n protected configurePlugin(uppy: Uppy): void {\n uppy.use(XHRUpload, {\n endpoint: `${this.config.endpoint}/api/upload`,\n fieldName: \"file\",\n formData: true,\n headers: this.auth.getAuthHeaders(),\n timeout: this.config.timeout,\n retries: this.config.retries,\n });\n }\n\n protected parseResult(result: unknown): UploadResult {\n const uppyResponse = result as {\n uploadURL: string;\n body?: unknown;\n };\n\n const body = uppyResponse.body;\n const uploadId = uppyResponse.uploadURL?.split(\"/\").pop() || \"\";\n\n return this.mapUploadResponse(body, uploadId);\n }\n\n protected getUploadSource(): string {\n return UPLOAD_SOURCE_XHR;\n }\n}\n"],"mappings":";;;;;AAMA,IAAa,mBAAb,cAAsC,kBAAkB;CACtD,
|
|
1
|
+
{"version":3,"file":"xhr-upload.js","names":[],"sources":["../../../src/upload/xhr-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport XHRUpload from \"@lumeweb/uppy-post-upload\";\nimport type { UploadResult } from \"@/types/upload\";\nimport { BaseUploadHandler } from \"./base-upload\";\nimport { UPLOAD_SOURCE_XHR } from \"./constants\";\n\nexport class XHRUploadHandler extends BaseUploadHandler {\n protected async configurePlugin(uppy: Uppy): Promise<void> {\n uppy.use(XHRUpload, {\n endpoint: `${this.config.endpoint}/api/upload`,\n fieldName: \"file\",\n formData: true,\n headers: await this.auth.getAuthHeaders(),\n timeout: this.config.timeout,\n retries: this.config.retries,\n });\n }\n\n protected parseResult(result: unknown): UploadResult {\n const uppyResponse = result as {\n uploadURL: string;\n body?: unknown;\n };\n\n const body = uppyResponse.body;\n const uploadId = uppyResponse.uploadURL?.split(\"/\").pop() || \"\";\n\n return this.mapUploadResponse(body, uploadId);\n }\n\n protected getUploadSource(): string {\n return UPLOAD_SOURCE_XHR;\n }\n}\n"],"mappings":";;;;;AAMA,IAAa,mBAAb,cAAsC,kBAAkB;CACtD,MAAgB,gBAAgB,MAA2B;AACzD,OAAK,IAAI,WAAW;GAClB,UAAU,GAAG,KAAK,OAAO,SAAS;GAClC,WAAW;GACX,UAAU;GACV,SAAS,MAAM,KAAK,KAAK,gBAAgB;GACzC,SAAS,KAAK,OAAO;GACrB,SAAS,KAAK,OAAO;GACtB,CAAC;;CAGJ,AAAU,YAAY,QAA+B;EACnD,MAAM,eAAe;EAKrB,MAAM,OAAO,aAAa;EAC1B,MAAM,WAAW,aAAa,WAAW,MAAM,IAAI,CAAC,KAAK,IAAI;AAE7D,SAAO,KAAK,kBAAkB,MAAM,SAAS;;CAG/C,AAAU,kBAA0B;AAClC,SAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumeweb/pinner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"interface-datastore": "^10.0.1",
|
|
57
57
|
"interface-store": "^8.0.0",
|
|
58
58
|
"ipaddr.js": "^2.4.0",
|
|
59
|
+
"jwt-decode": "^4.0.0",
|
|
59
60
|
"ky": "^2.0.2",
|
|
60
61
|
"multiformats": "^14.0.0",
|
|
61
62
|
"nanoevents": "^9.1.0",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"progress-events": "^1.1.0",
|
|
64
65
|
"tus-js-client": "4.3.1",
|
|
65
66
|
"unstorage": "^1.17.5",
|
|
66
|
-
"@lumeweb/portal-sdk": "0.1.
|
|
67
|
+
"@lumeweb/portal-sdk": "0.1.7",
|
|
67
68
|
"@lumeweb/query-builder": "0.1.1",
|
|
68
69
|
"@lumeweb/uppy-post-upload": "0.1.2"
|
|
69
70
|
},
|