@le-space/browser 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/index.d.ts +75 -1
- package/index.js +176 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,25 @@ The package should stay UI-neutral. It should provide browser-safe helpers, but
|
|
|
13
13
|
it should not own app-specific Svelte state, prepaid product logic, or wallet
|
|
14
14
|
UX.
|
|
15
15
|
|
|
16
|
+
## Client Surface
|
|
17
|
+
|
|
18
|
+
The preferred public entrypoint is a typed browser client factory:
|
|
19
|
+
|
|
20
|
+
- `createAlephBrowserClient({ apiHost?, crnListUrl? })`
|
|
21
|
+
|
|
22
|
+
That client should remain small and stable. It currently owns:
|
|
23
|
+
|
|
24
|
+
- balance lookup
|
|
25
|
+
- CRN listing
|
|
26
|
+
- instance listing
|
|
27
|
+
- message envelope lookup
|
|
28
|
+
- deployment result inspection and polling
|
|
29
|
+
- Aleph message broadcast helpers
|
|
30
|
+
|
|
31
|
+
Lower-level helper functions remain exported too, but new extractions should
|
|
32
|
+
prefer hanging reusable behavior off the client surface unless there is a good
|
|
33
|
+
reason to keep them as standalone utilities.
|
|
34
|
+
|
|
16
35
|
## Planned v1 Scope
|
|
17
36
|
|
|
18
37
|
The first real extraction wave should cover:
|
|
@@ -23,6 +42,7 @@ The first real extraction wave should cover:
|
|
|
23
42
|
- balance fetch
|
|
24
43
|
- CRN fetch
|
|
25
44
|
- instance listing
|
|
45
|
+
- typed browser client factory
|
|
26
46
|
- Aleph message broadcast helpers
|
|
27
47
|
- deployment polling and result inspection
|
|
28
48
|
- runtime detail inspection
|
package/index.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ interface BrowserPackagePlan {
|
|
|
5
5
|
}
|
|
6
6
|
declare const BROWSER_PACKAGE_PLAN: BrowserPackagePlan;
|
|
7
7
|
type MessageStatus = 'processed' | 'pending' | 'rejected' | 'unknown';
|
|
8
|
+
type ReferenceStatus = MessageStatus | 'missing';
|
|
9
|
+
type AlephSenderChain = 'ETH';
|
|
10
|
+
type AlephMessageType = 'INSTANCE' | 'FORGET' | 'AGGREGATE';
|
|
8
11
|
interface BalanceResponse {
|
|
9
12
|
address: string;
|
|
10
13
|
balance: string;
|
|
@@ -115,6 +118,66 @@ interface InstanceMessage {
|
|
|
115
118
|
confirmed?: boolean;
|
|
116
119
|
status?: string;
|
|
117
120
|
}
|
|
121
|
+
interface AlephMessageEnvelope {
|
|
122
|
+
status?: unknown;
|
|
123
|
+
type?: unknown;
|
|
124
|
+
error_code?: unknown;
|
|
125
|
+
details?: unknown;
|
|
126
|
+
message?: {
|
|
127
|
+
type?: unknown;
|
|
128
|
+
} | null;
|
|
129
|
+
messages?: Array<{
|
|
130
|
+
type?: unknown;
|
|
131
|
+
}> | null;
|
|
132
|
+
[key: string]: unknown;
|
|
133
|
+
}
|
|
134
|
+
interface MessageReference {
|
|
135
|
+
itemHash: string;
|
|
136
|
+
status: ReferenceStatus;
|
|
137
|
+
type: string | null;
|
|
138
|
+
}
|
|
139
|
+
interface DeploymentInspectionResult {
|
|
140
|
+
status: MessageStatus;
|
|
141
|
+
errorCode: number | null;
|
|
142
|
+
details: Record<string, unknown> | null;
|
|
143
|
+
rejectionReason: string | null;
|
|
144
|
+
references: MessageReference[];
|
|
145
|
+
}
|
|
146
|
+
interface AlephBroadcastMessage {
|
|
147
|
+
sender: string;
|
|
148
|
+
chain: AlephSenderChain;
|
|
149
|
+
signature: string;
|
|
150
|
+
type: AlephMessageType;
|
|
151
|
+
item_hash: string;
|
|
152
|
+
item_type: 'inline';
|
|
153
|
+
item_content: string;
|
|
154
|
+
time: number;
|
|
155
|
+
channel: string;
|
|
156
|
+
}
|
|
157
|
+
interface AlephBroadcastResponse {
|
|
158
|
+
publication_status?: {
|
|
159
|
+
status: string;
|
|
160
|
+
failed?: unknown[];
|
|
161
|
+
};
|
|
162
|
+
message_status?: MessageStatus;
|
|
163
|
+
[key: string]: unknown;
|
|
164
|
+
}
|
|
165
|
+
interface BroadcastResult {
|
|
166
|
+
response: AlephBroadcastResponse;
|
|
167
|
+
httpStatus: number;
|
|
168
|
+
}
|
|
169
|
+
interface AlephBrowserClient {
|
|
170
|
+
apiHost: string;
|
|
171
|
+
crnListUrl: string;
|
|
172
|
+
fetchBalance(address: string): Promise<BalanceResponse>;
|
|
173
|
+
fetchCrns(): Promise<Crn[]>;
|
|
174
|
+
fetchInstances(address: string): Promise<InstanceMessage[]>;
|
|
175
|
+
fetchMessageEnvelope(itemHash: string): Promise<AlephMessageEnvelope | null>;
|
|
176
|
+
inspectDeploymentResult(itemHash: string, rootfsRef?: string): Promise<DeploymentInspectionResult>;
|
|
177
|
+
waitForDeploymentResult(itemHash: string, rootfsRef?: string, attempts?: number, delayMs?: number): Promise<DeploymentInspectionResult>;
|
|
178
|
+
broadcastInstanceMessage(message: AlephBroadcastMessage, sync?: boolean): Promise<BroadcastResult>;
|
|
179
|
+
broadcastAlephMessage(message: AlephBroadcastMessage, sync?: boolean): Promise<BroadcastResult>;
|
|
180
|
+
}
|
|
118
181
|
interface RootfsRequiredPortForward {
|
|
119
182
|
port: number;
|
|
120
183
|
tcp?: boolean;
|
|
@@ -161,6 +224,17 @@ declare function normalizeMessageStatus(status: unknown): MessageStatus;
|
|
|
161
224
|
declare function fetchBalance(address: string, apiHost?: string): Promise<BalanceResponse>;
|
|
162
225
|
declare function fetchCrns(url?: string): Promise<Crn[]>;
|
|
163
226
|
declare function fetchInstances(address: string, apiHost?: string): Promise<InstanceMessage[]>;
|
|
227
|
+
declare function fetchMessageEnvelope(itemHash: string, apiHost?: string): Promise<AlephMessageEnvelope | null>;
|
|
228
|
+
declare function inspectDeploymentResult(itemHash: string, rootfsRef?: string, apiHost?: string): Promise<DeploymentInspectionResult>;
|
|
229
|
+
declare function waitForDeploymentResult(itemHash: string, rootfsRef?: string, apiHost?: string, attempts?: number, delayMs?: number): Promise<DeploymentInspectionResult>;
|
|
230
|
+
declare function broadcastInstanceMessage(message: AlephBroadcastMessage, apiHost?: string, sync?: boolean): Promise<BroadcastResult>;
|
|
231
|
+
declare function broadcastAlephMessage(message: AlephBroadcastMessage, apiHost?: string, sync?: boolean): Promise<BroadcastResult>;
|
|
232
|
+
|
|
233
|
+
interface CreateAlephBrowserClientOptions {
|
|
234
|
+
apiHost?: string;
|
|
235
|
+
crnListUrl?: string;
|
|
236
|
+
}
|
|
237
|
+
declare function createAlephBrowserClient(options?: CreateAlephBrowserClientOptions): AlephBrowserClient;
|
|
164
238
|
|
|
165
239
|
declare const ITEM_HASH_RE: RegExp;
|
|
166
240
|
declare const DEFAULT_ROOTFS_MANIFEST_URL = "./rootfs-manifest.json";
|
|
@@ -177,4 +251,4 @@ declare const DEFAULT_ALEPH_AGGREGATE_ADDRESS = "0xFba561a84A537fCaa567bb7A2257e
|
|
|
177
251
|
declare function parseInstancePricing(payload: unknown): InstancePricing;
|
|
178
252
|
declare function fetchInstancePricing(apiHost?: string, aggregateAddress?: string): Promise<PricingState>;
|
|
179
253
|
|
|
180
|
-
export { BROWSER_PACKAGE_PLAN, type BalanceResponse, type BrowserExtractionPhase, type BrowserPackagePlan, type ComputeUnit, type Crn, type CrnListResponse, type CrnLocation, type CrnUsage, DEFAULT_ALEPH_AGGREGATE_ADDRESS, 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 InstancePricing, type LoadRootfsManifestOptions, type MessageStatus, type PaymentMode, type Price, type PricingState, type RootfsManifest, type RootfsManifestState, type RootfsRequiredPortForward, type RootfsResolution, type Tier, fetchBalance, fetchCrns, fetchInstancePricing, fetchInstances, fetchWithTimeout, loadRootfsManifest, normalizeMessageStatus, parseInstancePricing, resolveRootfsReference, validateRootfsManifest, verifyRootfsExists };
|
|
254
|
+
export { type AlephBroadcastMessage, type AlephBroadcastResponse, type AlephBrowserClient, type AlephMessageEnvelope, type AlephMessageType, type AlephSenderChain, BROWSER_PACKAGE_PLAN, type BalanceResponse, type BroadcastResult, type BrowserExtractionPhase, type BrowserPackagePlan, type ComputeUnit, type CreateAlephBrowserClientOptions, type Crn, type CrnListResponse, type CrnLocation, type CrnUsage, DEFAULT_ALEPH_AGGREGATE_ADDRESS, DEFAULT_ALEPH_API_HOST, DEFAULT_CRN_LIST_URL, DEFAULT_IPFS_GATEWAY_BASE_URL, DEFAULT_ROOTFS_MANIFEST_URL, type DeploymentInspectionResult, type GatewayProbeStatus, ITEM_HASH_RE, type InstanceMessage, type InstancePricing, type LoadRootfsManifestOptions, type MessageReference, type MessageStatus, type PaymentMode, type Price, type PricingState, type ReferenceStatus, type RootfsManifest, type RootfsManifestState, type RootfsRequiredPortForward, type RootfsResolution, type Tier, broadcastAlephMessage, broadcastInstanceMessage, createAlephBrowserClient, fetchBalance, fetchCrns, fetchInstancePricing, fetchInstances, fetchMessageEnvelope, fetchWithTimeout, inspectDeploymentResult, loadRootfsManifest, normalizeMessageStatus, parseInstancePricing, resolveRootfsReference, validateRootfsManifest, verifyRootfsExists, waitForDeploymentResult };
|
package/index.js
CHANGED
|
@@ -89,6 +89,175 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
89
89
|
status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : message.status
|
|
90
90
|
}));
|
|
91
91
|
}
|
|
92
|
+
function messageTypeFromEnvelope(payload) {
|
|
93
|
+
if (!payload) return null;
|
|
94
|
+
const type = payload.type ?? payload.message?.type ?? (Array.isArray(payload.messages) ? payload.messages[0]?.type : void 0);
|
|
95
|
+
return typeof type === "string" ? type.toUpperCase() : null;
|
|
96
|
+
}
|
|
97
|
+
function extractReferenceHashes(details) {
|
|
98
|
+
if (!details || typeof details !== "object" || !("errors" in details)) return [];
|
|
99
|
+
const errors = details.errors;
|
|
100
|
+
if (!Array.isArray(errors)) return [];
|
|
101
|
+
return errors.filter((value) => typeof value === "string");
|
|
102
|
+
}
|
|
103
|
+
function describeRejectedDeployment(payload, references, rootfsRef) {
|
|
104
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
105
|
+
const pendingReferences = references.filter((reference) => reference.status === "pending");
|
|
106
|
+
const missingReferences = references.filter((reference) => reference.status === "missing");
|
|
107
|
+
const rootfsReference = references.find((reference) => reference.itemHash === rootfsRef);
|
|
108
|
+
if (rootfsReference?.status === "pending") {
|
|
109
|
+
return `Aleph rejected this deployment because the referenced rootfs STORE message ${rootfsReference.itemHash} is still pending and cannot yet be used by an instance. Wait for that STORE message to process, then deploy again.`;
|
|
110
|
+
}
|
|
111
|
+
if (pendingReferences.length > 0) {
|
|
112
|
+
return `Aleph rejected this deployment because referenced message(s) are still pending: ${pendingReferences.map((reference) => reference.itemHash).join(", ")}.`;
|
|
113
|
+
}
|
|
114
|
+
if (missingReferences.length > 0) {
|
|
115
|
+
return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
|
|
116
|
+
}
|
|
117
|
+
const referencedHashes = extractReferenceHashes(payload.details);
|
|
118
|
+
if (referencedHashes.length > 0) {
|
|
119
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
|
|
120
|
+
}
|
|
121
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
|
|
122
|
+
}
|
|
123
|
+
async function fetchMessageEnvelope(itemHash, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
124
|
+
const response = await fetchWithTimeout(`${apiHost}/api/v0/messages/${itemHash}`, {
|
|
125
|
+
cache: "no-cache"
|
|
126
|
+
});
|
|
127
|
+
if (response.status === 404) return null;
|
|
128
|
+
if (!response.ok) throw new Error(`Message lookup failed: ${response.status}`);
|
|
129
|
+
return await response.json();
|
|
130
|
+
}
|
|
131
|
+
async function fetchReference(itemHash, apiHost) {
|
|
132
|
+
const payload = await fetchMessageEnvelope(itemHash, apiHost);
|
|
133
|
+
if (!payload) {
|
|
134
|
+
return {
|
|
135
|
+
itemHash,
|
|
136
|
+
status: "missing",
|
|
137
|
+
type: null
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
itemHash,
|
|
142
|
+
status: normalizeMessageStatus(payload.status),
|
|
143
|
+
type: messageTypeFromEnvelope(payload)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function inspectDeploymentResult(itemHash, rootfsRef, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
147
|
+
const payload = await fetchMessageEnvelope(itemHash, apiHost);
|
|
148
|
+
if (!payload) {
|
|
149
|
+
return {
|
|
150
|
+
status: "unknown",
|
|
151
|
+
errorCode: null,
|
|
152
|
+
details: null,
|
|
153
|
+
rejectionReason: `Deployment message ${itemHash} was not found on Aleph.`,
|
|
154
|
+
references: []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const relatedHashes = new Set(rootfsRef ? [rootfsRef] : []);
|
|
158
|
+
for (const referenceHash of extractReferenceHashes(payload.details)) {
|
|
159
|
+
relatedHashes.add(referenceHash);
|
|
160
|
+
}
|
|
161
|
+
const references = await Promise.all(Array.from(relatedHashes).map((hash) => fetchReference(hash, apiHost)));
|
|
162
|
+
const status = normalizeMessageStatus(payload.status);
|
|
163
|
+
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
164
|
+
const details = payload.details && typeof payload.details === "object" ? payload.details : null;
|
|
165
|
+
return {
|
|
166
|
+
status,
|
|
167
|
+
errorCode,
|
|
168
|
+
details,
|
|
169
|
+
rejectionReason: status === "rejected" ? describeRejectedDeployment(payload, references, rootfsRef) : null,
|
|
170
|
+
references
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function waitForDeploymentResult(itemHash, rootfsRef, apiHost = DEFAULT_ALEPH_API_HOST, attempts = 15, delayMs = 2e3) {
|
|
174
|
+
let lastResult = await inspectDeploymentResult(itemHash, rootfsRef, apiHost);
|
|
175
|
+
for (let attempt = 1; attempt < attempts; attempt += 1) {
|
|
176
|
+
if (lastResult.status === "processed" || lastResult.status === "rejected") {
|
|
177
|
+
return lastResult;
|
|
178
|
+
}
|
|
179
|
+
await new Promise((resolve) => globalThis.setTimeout(resolve, delayMs));
|
|
180
|
+
lastResult = await inspectDeploymentResult(itemHash, rootfsRef, apiHost);
|
|
181
|
+
}
|
|
182
|
+
return lastResult;
|
|
183
|
+
}
|
|
184
|
+
function isInvalidMessageFormatResponse(response, payload) {
|
|
185
|
+
if (response.status !== 422) return false;
|
|
186
|
+
const details = payload.details;
|
|
187
|
+
if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
|
|
188
|
+
if (details && typeof details === "object") {
|
|
189
|
+
const detailMessage = details.message;
|
|
190
|
+
if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
async function postBroadcastPayload(body, apiHost) {
|
|
195
|
+
const rawResponse = await fetchWithTimeout(`${apiHost}/api/v0/messages`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "content-type": "application/json" },
|
|
198
|
+
body: JSON.stringify(body)
|
|
199
|
+
});
|
|
200
|
+
const response = await rawResponse.json().catch(() => ({}));
|
|
201
|
+
return {
|
|
202
|
+
response,
|
|
203
|
+
httpStatus: rawResponse.status,
|
|
204
|
+
rawResponse
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function broadcastInstanceMessage(message, apiHost = DEFAULT_ALEPH_API_HOST, sync = false) {
|
|
208
|
+
const attempts = [{ sync, message }, { ...message, sync }, { ...message }];
|
|
209
|
+
for (let index = 0; index < attempts.length; index += 1) {
|
|
210
|
+
const result = await postBroadcastPayload(attempts[index], apiHost);
|
|
211
|
+
if (result.rawResponse.ok || result.httpStatus === 202) {
|
|
212
|
+
return {
|
|
213
|
+
response: result.response,
|
|
214
|
+
httpStatus: result.httpStatus
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const canRetry = index < attempts.length - 1 && isInvalidMessageFormatResponse(result.rawResponse, result.response);
|
|
218
|
+
if (!canRetry) {
|
|
219
|
+
throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
throw new Error("Broadcast failed: no compatible request format was accepted");
|
|
223
|
+
}
|
|
224
|
+
async function broadcastAlephMessage(message, apiHost = DEFAULT_ALEPH_API_HOST, sync = false) {
|
|
225
|
+
return broadcastInstanceMessage(message, apiHost, sync);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/client.ts
|
|
229
|
+
function createAlephBrowserClient(options = {}) {
|
|
230
|
+
const apiHost = options.apiHost ?? DEFAULT_ALEPH_API_HOST;
|
|
231
|
+
const crnListUrl = options.crnListUrl ?? DEFAULT_CRN_LIST_URL;
|
|
232
|
+
return {
|
|
233
|
+
apiHost,
|
|
234
|
+
crnListUrl,
|
|
235
|
+
fetchBalance(address) {
|
|
236
|
+
return fetchBalance(address, apiHost);
|
|
237
|
+
},
|
|
238
|
+
fetchCrns() {
|
|
239
|
+
return fetchCrns(crnListUrl);
|
|
240
|
+
},
|
|
241
|
+
fetchInstances(address) {
|
|
242
|
+
return fetchInstances(address, apiHost);
|
|
243
|
+
},
|
|
244
|
+
fetchMessageEnvelope(itemHash) {
|
|
245
|
+
return fetchMessageEnvelope(itemHash, apiHost);
|
|
246
|
+
},
|
|
247
|
+
inspectDeploymentResult(itemHash, rootfsRef) {
|
|
248
|
+
return inspectDeploymentResult(itemHash, rootfsRef, apiHost);
|
|
249
|
+
},
|
|
250
|
+
waitForDeploymentResult(itemHash, rootfsRef, attempts, delayMs) {
|
|
251
|
+
return waitForDeploymentResult(itemHash, rootfsRef, apiHost, attempts, delayMs);
|
|
252
|
+
},
|
|
253
|
+
broadcastInstanceMessage(message, sync) {
|
|
254
|
+
return broadcastInstanceMessage(message, apiHost, sync);
|
|
255
|
+
},
|
|
256
|
+
broadcastAlephMessage(message, sync) {
|
|
257
|
+
return broadcastAlephMessage(message, apiHost, sync);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
92
261
|
|
|
93
262
|
// src/rootfs.ts
|
|
94
263
|
var ITEM_HASH_RE = /^[a-fA-F0-9]{64}$/u;
|
|
@@ -307,15 +476,21 @@ export {
|
|
|
307
476
|
DEFAULT_IPFS_GATEWAY_BASE_URL,
|
|
308
477
|
DEFAULT_ROOTFS_MANIFEST_URL,
|
|
309
478
|
ITEM_HASH_RE,
|
|
479
|
+
broadcastAlephMessage,
|
|
480
|
+
broadcastInstanceMessage,
|
|
481
|
+
createAlephBrowserClient,
|
|
310
482
|
fetchBalance,
|
|
311
483
|
fetchCrns,
|
|
312
484
|
fetchInstancePricing,
|
|
313
485
|
fetchInstances,
|
|
486
|
+
fetchMessageEnvelope,
|
|
314
487
|
fetchWithTimeout,
|
|
488
|
+
inspectDeploymentResult,
|
|
315
489
|
loadRootfsManifest,
|
|
316
490
|
normalizeMessageStatus,
|
|
317
491
|
parseInstancePricing,
|
|
318
492
|
resolveRootfsReference,
|
|
319
493
|
validateRootfsManifest,
|
|
320
|
-
verifyRootfsExists
|
|
494
|
+
verifyRootfsExists,
|
|
495
|
+
waitForDeploymentResult
|
|
321
496
|
};
|