@le-space/browser 0.1.26 → 0.1.28

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.
Files changed (4) hide show
  1. package/README.md +21 -0
  2. package/index.d.ts +67 -1
  3. package/index.js +116 -0
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -13,6 +13,26 @@ 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
+ - scheduler allocation lookup
29
+ - deployment result inspection and polling
30
+ - Aleph message broadcast helpers
31
+
32
+ Lower-level helper functions remain exported too, but new extractions should
33
+ prefer hanging reusable behavior off the client surface unless there is a good
34
+ reason to keep them as standalone utilities.
35
+
16
36
  ## Planned v1 Scope
17
37
 
18
38
  The first real extraction wave should cover:
@@ -23,6 +43,7 @@ The first real extraction wave should cover:
23
43
  - balance fetch
24
44
  - CRN fetch
25
45
  - instance listing
46
+ - typed browser client factory
26
47
  - Aleph message broadcast helpers
27
48
  - deployment polling and result inspection
28
49
  - runtime detail inspection
package/index.d.ts CHANGED
@@ -6,6 +6,8 @@ interface BrowserPackagePlan {
6
6
  declare const BROWSER_PACKAGE_PLAN: BrowserPackagePlan;
7
7
  type MessageStatus = 'processed' | 'pending' | 'rejected' | 'unknown';
8
8
  type ReferenceStatus = MessageStatus | 'missing';
9
+ type AlephSenderChain = 'ETH';
10
+ type AlephMessageType = 'INSTANCE' | 'FORGET' | 'AGGREGATE';
9
11
  interface BalanceResponse {
10
12
  address: string;
11
13
  balance: string;
@@ -141,6 +143,60 @@ interface DeploymentInspectionResult {
141
143
  rejectionReason: string | null;
142
144
  references: MessageReference[];
143
145
  }
146
+ interface InstanceAllocationNode {
147
+ node_id?: string;
148
+ url?: string;
149
+ ipv6?: string | null;
150
+ supports_ipv6?: boolean;
151
+ }
152
+ interface InstanceAllocationPeriod {
153
+ start_timestamp?: string;
154
+ duration_seconds?: number;
155
+ }
156
+ interface InstanceAllocation {
157
+ source: 'scheduler' | 'manual';
158
+ crnHash?: string | null;
159
+ crnUrl?: string | null;
160
+ node?: InstanceAllocationNode | null;
161
+ vmIpv6?: string | null;
162
+ period?: InstanceAllocationPeriod | null;
163
+ }
164
+ interface AlephBroadcastMessage {
165
+ sender: string;
166
+ chain: AlephSenderChain;
167
+ signature: string;
168
+ type: AlephMessageType;
169
+ item_hash: string;
170
+ item_type: 'inline';
171
+ item_content: string;
172
+ time: number;
173
+ channel: string;
174
+ }
175
+ interface AlephBroadcastResponse {
176
+ publication_status?: {
177
+ status: string;
178
+ failed?: unknown[];
179
+ };
180
+ message_status?: MessageStatus;
181
+ [key: string]: unknown;
182
+ }
183
+ interface BroadcastResult {
184
+ response: AlephBroadcastResponse;
185
+ httpStatus: number;
186
+ }
187
+ interface AlephBrowserClient {
188
+ apiHost: string;
189
+ crnListUrl: string;
190
+ fetchBalance(address: string): Promise<BalanceResponse>;
191
+ fetchCrns(): Promise<Crn[]>;
192
+ fetchInstances(address: string): Promise<InstanceMessage[]>;
193
+ fetchMessageEnvelope(itemHash: string): Promise<AlephMessageEnvelope | null>;
194
+ fetchSchedulerAllocation(itemHash: string): Promise<InstanceAllocation | null>;
195
+ inspectDeploymentResult(itemHash: string, rootfsRef?: string): Promise<DeploymentInspectionResult>;
196
+ waitForDeploymentResult(itemHash: string, rootfsRef?: string, attempts?: number, delayMs?: number): Promise<DeploymentInspectionResult>;
197
+ broadcastInstanceMessage(message: AlephBroadcastMessage, sync?: boolean): Promise<BroadcastResult>;
198
+ broadcastAlephMessage(message: AlephBroadcastMessage, sync?: boolean): Promise<BroadcastResult>;
199
+ }
144
200
  interface RootfsRequiredPortForward {
145
201
  port: number;
146
202
  tcp?: boolean;
@@ -183,13 +239,23 @@ declare function fetchWithTimeout(input: RequestInfo | URL, init?: RequestInit,
183
239
 
184
240
  declare const DEFAULT_ALEPH_API_HOST = "https://api2.aleph.im";
185
241
  declare const DEFAULT_CRN_LIST_URL = "https://crns-list.aleph.sh/crns.json";
242
+ declare const DEFAULT_ALEPH_SCHEDULER_API_HOST = "https://scheduler.api.aleph.cloud";
186
243
  declare function normalizeMessageStatus(status: unknown): MessageStatus;
187
244
  declare function fetchBalance(address: string, apiHost?: string): Promise<BalanceResponse>;
188
245
  declare function fetchCrns(url?: string): Promise<Crn[]>;
189
246
  declare function fetchInstances(address: string, apiHost?: string): Promise<InstanceMessage[]>;
247
+ declare function fetchSchedulerAllocation(itemHash: string, schedulerApiHost?: string): Promise<InstanceAllocation | null>;
190
248
  declare function fetchMessageEnvelope(itemHash: string, apiHost?: string): Promise<AlephMessageEnvelope | null>;
191
249
  declare function inspectDeploymentResult(itemHash: string, rootfsRef?: string, apiHost?: string): Promise<DeploymentInspectionResult>;
192
250
  declare function waitForDeploymentResult(itemHash: string, rootfsRef?: string, apiHost?: string, attempts?: number, delayMs?: number): Promise<DeploymentInspectionResult>;
251
+ declare function broadcastInstanceMessage(message: AlephBroadcastMessage, apiHost?: string, sync?: boolean): Promise<BroadcastResult>;
252
+ declare function broadcastAlephMessage(message: AlephBroadcastMessage, apiHost?: string, sync?: boolean): Promise<BroadcastResult>;
253
+
254
+ interface CreateAlephBrowserClientOptions {
255
+ apiHost?: string;
256
+ crnListUrl?: string;
257
+ }
258
+ declare function createAlephBrowserClient(options?: CreateAlephBrowserClientOptions): AlephBrowserClient;
193
259
 
194
260
  declare const ITEM_HASH_RE: RegExp;
195
261
  declare const DEFAULT_ROOTFS_MANIFEST_URL = "./rootfs-manifest.json";
@@ -206,4 +272,4 @@ declare const DEFAULT_ALEPH_AGGREGATE_ADDRESS = "0xFba561a84A537fCaa567bb7A2257e
206
272
  declare function parseInstancePricing(payload: unknown): InstancePricing;
207
273
  declare function fetchInstancePricing(apiHost?: string, aggregateAddress?: string): Promise<PricingState>;
208
274
 
209
- export { type AlephMessageEnvelope, 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 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, fetchBalance, fetchCrns, fetchInstancePricing, fetchInstances, fetchMessageEnvelope, fetchWithTimeout, inspectDeploymentResult, loadRootfsManifest, normalizeMessageStatus, parseInstancePricing, resolveRootfsReference, validateRootfsManifest, verifyRootfsExists, waitForDeploymentResult };
275
+ 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_ALEPH_SCHEDULER_API_HOST, DEFAULT_CRN_LIST_URL, DEFAULT_IPFS_GATEWAY_BASE_URL, DEFAULT_ROOTFS_MANIFEST_URL, type DeploymentInspectionResult, type GatewayProbeStatus, ITEM_HASH_RE, type InstanceAllocation, type InstanceAllocationNode, type InstanceAllocationPeriod, 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, fetchSchedulerAllocation, fetchWithTimeout, inspectDeploymentResult, loadRootfsManifest, normalizeMessageStatus, parseInstancePricing, resolveRootfsReference, validateRootfsManifest, verifyRootfsExists, waitForDeploymentResult };
package/index.js CHANGED
@@ -50,6 +50,7 @@ async function fetchWithTimeout(input, init = {}, timeoutMs = 15e3) {
50
50
  // src/aleph-api.ts
51
51
  var DEFAULT_ALEPH_API_HOST = "https://api2.aleph.im";
52
52
  var DEFAULT_CRN_LIST_URL = "https://crns-list.aleph.sh/crns.json";
53
+ var DEFAULT_ALEPH_SCHEDULER_API_HOST = "https://scheduler.api.aleph.cloud";
53
54
  function normalizeMessageStatus(status) {
54
55
  if (typeof status !== "string") return "unknown";
55
56
  const normalized = status.toLowerCase();
@@ -58,6 +59,12 @@ function normalizeMessageStatus(status) {
58
59
  }
59
60
  return "unknown";
60
61
  }
62
+ function asString(value) {
63
+ return typeof value === "string" && value.trim() ? value : null;
64
+ }
65
+ function asNumber(value) {
66
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
67
+ }
61
68
  async function fetchBalance(address, apiHost = DEFAULT_ALEPH_API_HOST) {
62
69
  const response = await fetchWithTimeout(`${apiHost}/api/v0/addresses/${address}/balance`, {
63
70
  cache: "no-cache"
@@ -89,6 +96,30 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
89
96
  status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : message.status
90
97
  }));
91
98
  }
99
+ async function fetchSchedulerAllocation(itemHash, schedulerApiHost = DEFAULT_ALEPH_SCHEDULER_API_HOST) {
100
+ const response = await fetchWithTimeout(`${schedulerApiHost}/api/v0/allocation/${itemHash}`, {
101
+ cache: "no-cache"
102
+ });
103
+ if (response.status === 404) return null;
104
+ if (!response.ok) throw new Error(`Scheduler allocation request failed: ${response.status}`);
105
+ const payload = await response.json();
106
+ const node = payload.node;
107
+ return {
108
+ source: "scheduler",
109
+ crnUrl: asString(node?.url),
110
+ node: node ? {
111
+ node_id: asString(node.node_id) ?? void 0,
112
+ url: asString(node.url) ?? void 0,
113
+ ipv6: asString(node.ipv6),
114
+ supports_ipv6: typeof node.supports_ipv6 === "boolean" ? node.supports_ipv6 : void 0
115
+ } : null,
116
+ vmIpv6: asString(payload.vm_ipv6),
117
+ period: payload.period ? {
118
+ start_timestamp: asString(payload.period.start_timestamp) ?? void 0,
119
+ duration_seconds: asNumber(payload.period.duration_seconds) ?? void 0
120
+ } : null
121
+ };
122
+ }
92
123
  function messageTypeFromEnvelope(payload) {
93
124
  if (!payload) return null;
94
125
  const type = payload.type ?? payload.message?.type ?? (Array.isArray(payload.messages) ? payload.messages[0]?.type : void 0);
@@ -181,6 +212,86 @@ async function waitForDeploymentResult(itemHash, rootfsRef, apiHost = DEFAULT_AL
181
212
  }
182
213
  return lastResult;
183
214
  }
215
+ function isInvalidMessageFormatResponse(response, payload) {
216
+ if (response.status !== 422) return false;
217
+ const details = payload.details;
218
+ if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
219
+ if (details && typeof details === "object") {
220
+ const detailMessage = details.message;
221
+ if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
222
+ }
223
+ return false;
224
+ }
225
+ async function postBroadcastPayload(body, apiHost) {
226
+ const rawResponse = await fetchWithTimeout(`${apiHost}/api/v0/messages`, {
227
+ method: "POST",
228
+ headers: { "content-type": "application/json" },
229
+ body: JSON.stringify(body)
230
+ });
231
+ const response = await rawResponse.json().catch(() => ({}));
232
+ return {
233
+ response,
234
+ httpStatus: rawResponse.status,
235
+ rawResponse
236
+ };
237
+ }
238
+ async function broadcastInstanceMessage(message, apiHost = DEFAULT_ALEPH_API_HOST, sync = false) {
239
+ const attempts = [{ sync, message }, { ...message, sync }, { ...message }];
240
+ for (let index = 0; index < attempts.length; index += 1) {
241
+ const result = await postBroadcastPayload(attempts[index], apiHost);
242
+ if (result.rawResponse.ok || result.httpStatus === 202) {
243
+ return {
244
+ response: result.response,
245
+ httpStatus: result.httpStatus
246
+ };
247
+ }
248
+ const canRetry = index < attempts.length - 1 && isInvalidMessageFormatResponse(result.rawResponse, result.response);
249
+ if (!canRetry) {
250
+ throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response)}`);
251
+ }
252
+ }
253
+ throw new Error("Broadcast failed: no compatible request format was accepted");
254
+ }
255
+ async function broadcastAlephMessage(message, apiHost = DEFAULT_ALEPH_API_HOST, sync = false) {
256
+ return broadcastInstanceMessage(message, apiHost, sync);
257
+ }
258
+
259
+ // src/client.ts
260
+ function createAlephBrowserClient(options = {}) {
261
+ const apiHost = options.apiHost ?? DEFAULT_ALEPH_API_HOST;
262
+ const crnListUrl = options.crnListUrl ?? DEFAULT_CRN_LIST_URL;
263
+ return {
264
+ apiHost,
265
+ crnListUrl,
266
+ fetchBalance(address) {
267
+ return fetchBalance(address, apiHost);
268
+ },
269
+ fetchCrns() {
270
+ return fetchCrns(crnListUrl);
271
+ },
272
+ fetchInstances(address) {
273
+ return fetchInstances(address, apiHost);
274
+ },
275
+ fetchMessageEnvelope(itemHash) {
276
+ return fetchMessageEnvelope(itemHash, apiHost);
277
+ },
278
+ fetchSchedulerAllocation(itemHash) {
279
+ return fetchSchedulerAllocation(itemHash);
280
+ },
281
+ inspectDeploymentResult(itemHash, rootfsRef) {
282
+ return inspectDeploymentResult(itemHash, rootfsRef, apiHost);
283
+ },
284
+ waitForDeploymentResult(itemHash, rootfsRef, attempts, delayMs) {
285
+ return waitForDeploymentResult(itemHash, rootfsRef, apiHost, attempts, delayMs);
286
+ },
287
+ broadcastInstanceMessage(message, sync) {
288
+ return broadcastInstanceMessage(message, apiHost, sync);
289
+ },
290
+ broadcastAlephMessage(message, sync) {
291
+ return broadcastAlephMessage(message, apiHost, sync);
292
+ }
293
+ };
294
+ }
184
295
 
185
296
  // src/rootfs.ts
186
297
  var ITEM_HASH_RE = /^[a-fA-F0-9]{64}$/u;
@@ -395,15 +506,20 @@ export {
395
506
  BROWSER_PACKAGE_PLAN,
396
507
  DEFAULT_ALEPH_AGGREGATE_ADDRESS,
397
508
  DEFAULT_ALEPH_API_HOST,
509
+ DEFAULT_ALEPH_SCHEDULER_API_HOST,
398
510
  DEFAULT_CRN_LIST_URL,
399
511
  DEFAULT_IPFS_GATEWAY_BASE_URL,
400
512
  DEFAULT_ROOTFS_MANIFEST_URL,
401
513
  ITEM_HASH_RE,
514
+ broadcastAlephMessage,
515
+ broadcastInstanceMessage,
516
+ createAlephBrowserClient,
402
517
  fetchBalance,
403
518
  fetchCrns,
404
519
  fetchInstancePricing,
405
520
  fetchInstances,
406
521
  fetchMessageEnvelope,
522
+ fetchSchedulerAllocation,
407
523
  fetchWithTimeout,
408
524
  inspectDeploymentResult,
409
525
  loadRootfsManifest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/browser",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Shared browser-safe Aleph deployment and polling helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",