@le-space/browser 0.1.23 → 0.1.24
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/index.d.ts +48 -2
- package/index.js +203 -5
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -86,6 +86,43 @@ interface InstanceMessage {
|
|
|
86
86
|
confirmed?: boolean;
|
|
87
87
|
status?: string;
|
|
88
88
|
}
|
|
89
|
+
interface RootfsRequiredPortForward {
|
|
90
|
+
port: number;
|
|
91
|
+
tcp?: boolean;
|
|
92
|
+
udp?: boolean;
|
|
93
|
+
purpose?: string;
|
|
94
|
+
}
|
|
95
|
+
interface RootfsManifest {
|
|
96
|
+
profile?: string;
|
|
97
|
+
version: string;
|
|
98
|
+
rootfsInstallStrategy?: 'thin' | 'prebaked' | string;
|
|
99
|
+
requiresBootstrapNetwork?: boolean;
|
|
100
|
+
bootstrapSummary?: string;
|
|
101
|
+
requiredPortForwards?: RootfsRequiredPortForward[];
|
|
102
|
+
rootfsItemHash: string;
|
|
103
|
+
rootfsSizeMiB: number;
|
|
104
|
+
rootfsSourceSizeBytes?: number;
|
|
105
|
+
createdAt: string;
|
|
106
|
+
notes?: string;
|
|
107
|
+
}
|
|
108
|
+
interface RootfsManifestState {
|
|
109
|
+
manifest: RootfsManifest | null;
|
|
110
|
+
valid: boolean;
|
|
111
|
+
errors: string[];
|
|
112
|
+
}
|
|
113
|
+
type GatewayProbeStatus = 'reachable' | 'timeout' | 'error' | 'unavailable' | 'unknown';
|
|
114
|
+
interface RootfsResolution {
|
|
115
|
+
itemHash: string;
|
|
116
|
+
messageStatus: MessageStatus;
|
|
117
|
+
messageType: string | null;
|
|
118
|
+
cid: string | null;
|
|
119
|
+
receptionTime?: string | null;
|
|
120
|
+
rejectionErrorCode?: number | null;
|
|
121
|
+
rejectionReason?: string | null;
|
|
122
|
+
gatewayUrl: string | null;
|
|
123
|
+
gatewayStatus: GatewayProbeStatus;
|
|
124
|
+
gatewayError?: string | null;
|
|
125
|
+
}
|
|
89
126
|
|
|
90
127
|
declare function fetchWithTimeout(input: RequestInfo | URL, init?: RequestInit, timeoutMs?: number): Promise<Response>;
|
|
91
128
|
|
|
@@ -96,8 +133,17 @@ declare function fetchBalance(address: string, apiHost?: string): Promise<Balanc
|
|
|
96
133
|
declare function fetchCrns(url?: string): Promise<Crn[]>;
|
|
97
134
|
declare function fetchInstances(address: string, apiHost?: string): Promise<InstanceMessage[]>;
|
|
98
135
|
|
|
99
|
-
declare const
|
|
136
|
+
declare const ITEM_HASH_RE: RegExp;
|
|
137
|
+
declare const DEFAULT_ROOTFS_MANIFEST_URL = "./rootfs-manifest.json";
|
|
138
|
+
declare const DEFAULT_IPFS_GATEWAY_BASE_URL = "https://ipfs.aleph.cloud/ipfs/";
|
|
139
|
+
interface LoadRootfsManifestOptions {
|
|
140
|
+
baseUrl?: string | URL;
|
|
141
|
+
}
|
|
142
|
+
declare function validateRootfsManifest(manifest: RootfsManifest | null): RootfsManifestState;
|
|
143
|
+
declare function loadRootfsManifest(url?: string | URL, options?: LoadRootfsManifestOptions): Promise<RootfsManifestState>;
|
|
144
|
+
declare function verifyRootfsExists(itemHash: string, apiHost?: string): Promise<boolean>;
|
|
145
|
+
declare function resolveRootfsReference(itemHash: string, apiHost?: string, gatewayBaseUrl?: string): Promise<RootfsResolution | null>;
|
|
100
146
|
|
|
101
147
|
declare const BROWSER_PRICING_MODULE = "planned";
|
|
102
148
|
|
|
103
|
-
export { BROWSER_PACKAGE_PLAN, BROWSER_PRICING_MODULE,
|
|
149
|
+
export { BROWSER_PACKAGE_PLAN, BROWSER_PRICING_MODULE, type BalanceResponse, type BrowserExtractionPhase, type BrowserPackagePlan, type Crn, type CrnListResponse, type CrnLocation, type CrnUsage, DEFAULT_ALEPH_API_HOST, DEFAULT_CRN_LIST_URL, DEFAULT_IPFS_GATEWAY_BASE_URL, DEFAULT_ROOTFS_MANIFEST_URL, type GatewayProbeStatus, ITEM_HASH_RE, type InstanceMessage, type LoadRootfsManifestOptions, type MessageStatus, type PaymentMode, type RootfsManifest, type RootfsManifestState, type RootfsRequiredPortForward, type RootfsResolution, fetchBalance, fetchCrns, fetchInstances, fetchWithTimeout, loadRootfsManifest, normalizeMessageStatus, resolveRootfsReference, validateRootfsManifest, verifyRootfsExists };
|
package/index.js
CHANGED
|
@@ -9,8 +9,8 @@ async function fetchWithTimeout(input, init = {}, timeoutMs = 15e3) {
|
|
|
9
9
|
const controller = new AbortController();
|
|
10
10
|
const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);
|
|
11
11
|
try {
|
|
12
|
-
if (
|
|
13
|
-
const url = new URL(
|
|
12
|
+
if (input instanceof URL) {
|
|
13
|
+
const url = new URL(input.toString());
|
|
14
14
|
url.searchParams.set("_ts", String(Date.now()));
|
|
15
15
|
return await fetch(url, {
|
|
16
16
|
...init,
|
|
@@ -18,6 +18,20 @@ async function fetchWithTimeout(input, init = {}, timeoutMs = 15e3) {
|
|
|
18
18
|
signal: init.signal ?? controller.signal
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
if (typeof input === "string") {
|
|
22
|
+
let requestInput = input;
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(input, globalThis.location?.href);
|
|
25
|
+
url.searchParams.set("_ts", String(Date.now()));
|
|
26
|
+
requestInput = url;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return await fetch(requestInput, {
|
|
30
|
+
...init,
|
|
31
|
+
cache: init.cache ?? "no-store",
|
|
32
|
+
signal: init.signal ?? controller.signal
|
|
33
|
+
});
|
|
34
|
+
}
|
|
21
35
|
return await fetch(input, {
|
|
22
36
|
...init,
|
|
23
37
|
cache: init.cache ?? "no-store",
|
|
@@ -77,19 +91,203 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
// src/rootfs.ts
|
|
80
|
-
var
|
|
94
|
+
var ITEM_HASH_RE = /^[a-fA-F0-9]{64}$/u;
|
|
95
|
+
var DEFAULT_ROOTFS_MANIFEST_URL = "./rootfs-manifest.json";
|
|
96
|
+
var DEFAULT_IPFS_GATEWAY_BASE_URL = "https://ipfs.aleph.cloud/ipfs/";
|
|
97
|
+
function resolveManifestUrl(input, baseUrl) {
|
|
98
|
+
if (input instanceof URL) return input;
|
|
99
|
+
try {
|
|
100
|
+
return new URL(input, baseUrl ? String(baseUrl) : globalThis.location?.href);
|
|
101
|
+
} catch {
|
|
102
|
+
return input;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateRootfsManifest(manifest) {
|
|
106
|
+
const errors = [];
|
|
107
|
+
if (!manifest) {
|
|
108
|
+
return { manifest, valid: false, errors: ["Rootfs manifest is missing."] };
|
|
109
|
+
}
|
|
110
|
+
if (!manifest.version) errors.push("Rootfs manifest version is missing.");
|
|
111
|
+
if (manifest.rootfsInstallStrategy != null && manifest.rootfsInstallStrategy !== "thin" && manifest.rootfsInstallStrategy !== "prebaked") {
|
|
112
|
+
errors.push('Rootfs install strategy must be "thin" or "prebaked" when provided.');
|
|
113
|
+
}
|
|
114
|
+
if (manifest.requiresBootstrapNetwork != null && typeof manifest.requiresBootstrapNetwork !== "boolean") {
|
|
115
|
+
errors.push("Rootfs bootstrap network flag must be a boolean when provided.");
|
|
116
|
+
}
|
|
117
|
+
if (manifest.bootstrapSummary != null && !manifest.bootstrapSummary.trim()) {
|
|
118
|
+
errors.push("Rootfs bootstrap summary must be non-empty when provided.");
|
|
119
|
+
}
|
|
120
|
+
if (manifest.requiredPortForwards != null) {
|
|
121
|
+
if (!Array.isArray(manifest.requiredPortForwards)) {
|
|
122
|
+
errors.push("Rootfs required port forwards must be an array when provided.");
|
|
123
|
+
} else {
|
|
124
|
+
manifest.requiredPortForwards.forEach((entry, index) => {
|
|
125
|
+
if (!entry || typeof entry !== "object") {
|
|
126
|
+
errors.push(`Rootfs required port forward #${index + 1} must be an object.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!Number.isInteger(entry.port) || entry.port < 1 || entry.port > 65535) {
|
|
130
|
+
errors.push(`Rootfs required port forward #${index + 1} must use a TCP/UDP port between 1 and 65535.`);
|
|
131
|
+
}
|
|
132
|
+
if (entry.tcp !== true && entry.udp !== true) {
|
|
133
|
+
errors.push(`Rootfs required port forward #${index + 1} must enable TCP or UDP.`);
|
|
134
|
+
}
|
|
135
|
+
if (entry.purpose != null && (typeof entry.purpose !== "string" || !entry.purpose.trim())) {
|
|
136
|
+
errors.push(`Rootfs required port forward #${index + 1} purpose must be non-empty when provided.`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!ITEM_HASH_RE.test(manifest.rootfsItemHash || "")) {
|
|
142
|
+
errors.push("Rootfs ItemHash must be a 64 character hex value.");
|
|
143
|
+
}
|
|
144
|
+
if (!Number.isInteger(manifest.rootfsSizeMiB) || manifest.rootfsSizeMiB <= 0) {
|
|
145
|
+
errors.push("Rootfs size must be a positive MiB integer.");
|
|
146
|
+
}
|
|
147
|
+
if (manifest.rootfsSourceSizeBytes != null && (!Number.isInteger(manifest.rootfsSourceSizeBytes) || manifest.rootfsSourceSizeBytes <= 0)) {
|
|
148
|
+
errors.push("Rootfs source size must be a positive byte integer when provided.");
|
|
149
|
+
}
|
|
150
|
+
if (!manifest.createdAt || Number.isNaN(new Date(manifest.createdAt).getTime())) {
|
|
151
|
+
errors.push("Rootfs creation date is missing or invalid.");
|
|
152
|
+
}
|
|
153
|
+
return { manifest, valid: errors.length === 0, errors };
|
|
154
|
+
}
|
|
155
|
+
async function loadRootfsManifest(url = DEFAULT_ROOTFS_MANIFEST_URL, options = {}) {
|
|
156
|
+
const response = await fetchWithTimeout(resolveManifestUrl(url, options.baseUrl), { cache: "no-cache" });
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error(`Rootfs manifest request failed: ${response.status}`);
|
|
159
|
+
}
|
|
160
|
+
return validateRootfsManifest(await response.json());
|
|
161
|
+
}
|
|
162
|
+
async function verifyRootfsExists(itemHash, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
163
|
+
if (!ITEM_HASH_RE.test(itemHash)) return false;
|
|
164
|
+
const response = await fetchWithTimeout(`${apiHost}/api/v0/messages/${itemHash}`, {
|
|
165
|
+
method: "GET",
|
|
166
|
+
cache: "no-cache"
|
|
167
|
+
});
|
|
168
|
+
if (response.status === 404) return false;
|
|
169
|
+
if (!response.ok) throw new Error(`Rootfs lookup failed: ${response.status}`);
|
|
170
|
+
const payload = await response.json();
|
|
171
|
+
const firstMessage = Array.isArray(payload.messages) ? payload.messages[0] : void 0;
|
|
172
|
+
const type = String(payload.type || payload.message?.type || firstMessage?.type || "").toUpperCase();
|
|
173
|
+
return type === "STORE";
|
|
174
|
+
}
|
|
175
|
+
function normalizeStatus(status) {
|
|
176
|
+
if (typeof status !== "string") return "unknown";
|
|
177
|
+
const normalized = status.toLowerCase();
|
|
178
|
+
if (normalized === "processed" || normalized === "pending" || normalized === "rejected") {
|
|
179
|
+
return normalized;
|
|
180
|
+
}
|
|
181
|
+
return "unknown";
|
|
182
|
+
}
|
|
183
|
+
function parseCidFromPayload(payload) {
|
|
184
|
+
const firstMessage = Array.isArray(payload.messages) && payload.messages[0] && typeof payload.messages[0] === "object" ? payload.messages[0] : null;
|
|
185
|
+
const directContent = firstMessage?.content && typeof firstMessage.content === "object" ? firstMessage.content : null;
|
|
186
|
+
if (typeof directContent?.item_hash === "string") {
|
|
187
|
+
return directContent.item_hash;
|
|
188
|
+
}
|
|
189
|
+
if (typeof firstMessage?.item_content === "string") {
|
|
190
|
+
try {
|
|
191
|
+
const itemContent = JSON.parse(firstMessage.item_content);
|
|
192
|
+
if (typeof itemContent.item_hash === "string") {
|
|
193
|
+
return itemContent.item_hash;
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
function parseRejectionReason(payload) {
|
|
202
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
203
|
+
const details = payload.details && typeof payload.details === "object" ? payload.details : null;
|
|
204
|
+
const rawErrors = Array.isArray(details?.errors) ? details.errors : [];
|
|
205
|
+
const firstError = rawErrors[0] && typeof rawErrors[0] === "object" ? rawErrors[0] : null;
|
|
206
|
+
if (firstError) {
|
|
207
|
+
const accountBalance = Number(firstError.account_balance);
|
|
208
|
+
const requiredBalance = Number(firstError.required_balance);
|
|
209
|
+
if (Number.isFinite(accountBalance) && Number.isFinite(requiredBalance)) {
|
|
210
|
+
const shortfall = requiredBalance - accountBalance;
|
|
211
|
+
return {
|
|
212
|
+
rejectionErrorCode: errorCode,
|
|
213
|
+
rejectionReason: shortfall > 0 ? `Rejected by Aleph for insufficient hold balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short.` : `Rejected by Aleph for insufficient hold balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required.`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
rejectionErrorCode: errorCode,
|
|
219
|
+
rejectionReason: errorCode != null ? `Rejected by Aleph (error code ${errorCode}).` : null
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function probeGateway(cid, gatewayBaseUrl = DEFAULT_IPFS_GATEWAY_BASE_URL) {
|
|
223
|
+
const gatewayUrl = new URL(cid, gatewayBaseUrl).toString();
|
|
224
|
+
try {
|
|
225
|
+
const response = await fetchWithTimeout(gatewayUrl, { method: "HEAD", cache: "no-store" }, 5e3);
|
|
226
|
+
return {
|
|
227
|
+
gatewayUrl,
|
|
228
|
+
gatewayStatus: response.ok ? "reachable" : "error",
|
|
229
|
+
gatewayError: response.ok ? null : `Gateway responded with ${response.status}.`
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error instanceof Error && error.message.includes("timed out")) {
|
|
233
|
+
return {
|
|
234
|
+
gatewayUrl,
|
|
235
|
+
gatewayStatus: "timeout",
|
|
236
|
+
gatewayError: error.message
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
gatewayUrl,
|
|
241
|
+
gatewayStatus: "unavailable",
|
|
242
|
+
gatewayError: error instanceof Error ? error.message : String(error)
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function resolveRootfsReference(itemHash, apiHost = DEFAULT_ALEPH_API_HOST, gatewayBaseUrl = DEFAULT_IPFS_GATEWAY_BASE_URL) {
|
|
247
|
+
if (!ITEM_HASH_RE.test(itemHash)) return null;
|
|
248
|
+
const response = await fetchWithTimeout(`${apiHost}/api/v0/messages/${itemHash}`, {
|
|
249
|
+
method: "GET",
|
|
250
|
+
cache: "no-cache"
|
|
251
|
+
});
|
|
252
|
+
if (response.status === 404) return null;
|
|
253
|
+
if (!response.ok) throw new Error(`Rootfs lookup failed: ${response.status}`);
|
|
254
|
+
const payload = await response.json();
|
|
255
|
+
const firstMessage = Array.isArray(payload.messages) && payload.messages[0] && typeof payload.messages[0] === "object" ? payload.messages[0] : null;
|
|
256
|
+
const messageObject = payload.message && typeof payload.message === "object" ? payload.message : null;
|
|
257
|
+
const cid = parseCidFromPayload(payload);
|
|
258
|
+
const rejection = normalizeStatus(payload.status) === "rejected" ? parseRejectionReason(payload) : { rejectionErrorCode: null, rejectionReason: null };
|
|
259
|
+
const gateway = cid ? await probeGateway(cid, gatewayBaseUrl) : { gatewayUrl: null, gatewayStatus: "unknown", gatewayError: null };
|
|
260
|
+
return {
|
|
261
|
+
itemHash,
|
|
262
|
+
messageStatus: normalizeStatus(payload.status),
|
|
263
|
+
messageType: String(payload.type || messageObject?.type || firstMessage?.type || "").toUpperCase() || null,
|
|
264
|
+
cid,
|
|
265
|
+
receptionTime: typeof payload.reception_time === "string" ? payload.reception_time : null,
|
|
266
|
+
rejectionErrorCode: rejection.rejectionErrorCode,
|
|
267
|
+
rejectionReason: rejection.rejectionReason,
|
|
268
|
+
gatewayUrl: gateway.gatewayUrl,
|
|
269
|
+
gatewayStatus: gateway.gatewayStatus,
|
|
270
|
+
gatewayError: gateway.gatewayError
|
|
271
|
+
};
|
|
272
|
+
}
|
|
81
273
|
|
|
82
274
|
// src/pricing.ts
|
|
83
275
|
var BROWSER_PRICING_MODULE = "planned";
|
|
84
276
|
export {
|
|
85
277
|
BROWSER_PACKAGE_PLAN,
|
|
86
278
|
BROWSER_PRICING_MODULE,
|
|
87
|
-
BROWSER_ROOTFS_MODULE,
|
|
88
279
|
DEFAULT_ALEPH_API_HOST,
|
|
89
280
|
DEFAULT_CRN_LIST_URL,
|
|
281
|
+
DEFAULT_IPFS_GATEWAY_BASE_URL,
|
|
282
|
+
DEFAULT_ROOTFS_MANIFEST_URL,
|
|
283
|
+
ITEM_HASH_RE,
|
|
90
284
|
fetchBalance,
|
|
91
285
|
fetchCrns,
|
|
92
286
|
fetchInstances,
|
|
93
287
|
fetchWithTimeout,
|
|
94
|
-
|
|
288
|
+
loadRootfsManifest,
|
|
289
|
+
normalizeMessageStatus,
|
|
290
|
+
resolveRootfsReference,
|
|
291
|
+
validateRootfsManifest,
|
|
292
|
+
verifyRootfsExists
|
|
95
293
|
};
|