@layr-labs/ecloud-sdk 0.2.1-dev → 0.3.0-dev

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/VERSION CHANGED
@@ -1,2 +1,2 @@
1
- version=0.2.1-dev
2
- commit=8a3a31467debcacba5623e266e82ba49309add26
1
+ version=0.3.0-dev
2
+ commit=f66f41c4db37c3eb732f093499934884d8132a71
package/dist/billing.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/client/common/auth/session.ts
34
+ var init_session = __esm({
35
+ "src/client/common/auth/session.ts"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
30
40
  // src/billing.ts
31
41
  var billing_exports = {};
32
42
  __export(billing_exports, {
@@ -74,25 +84,220 @@ async function calculateBillingAuthSignature(options) {
74
84
  return { signature, expiry };
75
85
  }
76
86
 
87
+ // src/client/common/auth/billingSession.ts
88
+ var BillingSessionError = class extends Error {
89
+ constructor(message, code, statusCode) {
90
+ super(message);
91
+ this.code = code;
92
+ this.statusCode = statusCode;
93
+ this.name = "BillingSessionError";
94
+ }
95
+ };
96
+ function stripHexPrefix(hex) {
97
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
98
+ }
99
+ async function parseErrorResponse(response) {
100
+ try {
101
+ const data = await response.json();
102
+ return data.error || response.statusText;
103
+ } catch {
104
+ return response.statusText;
105
+ }
106
+ }
107
+ async function loginToBillingApi(config, request) {
108
+ let response;
109
+ try {
110
+ response = await fetch(`${config.baseUrl}/auth/siwe/login`, {
111
+ method: "POST",
112
+ credentials: "include",
113
+ // Include cookies for session management
114
+ headers: {
115
+ "Content-Type": "application/json"
116
+ },
117
+ body: JSON.stringify({
118
+ message: request.message,
119
+ signature: stripHexPrefix(request.signature)
120
+ })
121
+ });
122
+ } catch (error) {
123
+ throw new BillingSessionError(
124
+ `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,
125
+ "NETWORK_ERROR"
126
+ );
127
+ }
128
+ if (!response.ok) {
129
+ const errorMessage = await parseErrorResponse(response);
130
+ const status = response.status;
131
+ if (status === 400) {
132
+ if (errorMessage.toLowerCase().includes("siwe")) {
133
+ throw new BillingSessionError(`Invalid SIWE message: ${errorMessage}`, "INVALID_MESSAGE", status);
134
+ }
135
+ throw new BillingSessionError(`Bad request: ${errorMessage}`, "INVALID_MESSAGE", status);
136
+ }
137
+ if (status === 401) {
138
+ throw new BillingSessionError(`Invalid signature: ${errorMessage}`, "INVALID_SIGNATURE", status);
139
+ }
140
+ throw new BillingSessionError(`Login failed: ${errorMessage}`, "UNKNOWN", status);
141
+ }
142
+ const data = await response.json();
143
+ return {
144
+ success: data.success,
145
+ address: data.address
146
+ };
147
+ }
148
+ async function getBillingApiSession(config) {
149
+ let response;
150
+ try {
151
+ response = await fetch(`${config.baseUrl}/auth/session`, {
152
+ method: "GET",
153
+ credentials: "include",
154
+ // Include cookies for session management
155
+ headers: {
156
+ "Content-Type": "application/json"
157
+ }
158
+ });
159
+ } catch {
160
+ return {
161
+ authenticated: false
162
+ };
163
+ }
164
+ if (response.status === 401) {
165
+ return {
166
+ authenticated: false
167
+ };
168
+ }
169
+ if (!response.ok) {
170
+ const errorMessage = await parseErrorResponse(response);
171
+ throw new BillingSessionError(`Failed to get session: ${errorMessage}`, "UNKNOWN", response.status);
172
+ }
173
+ const data = await response.json();
174
+ return {
175
+ authenticated: data.authenticated,
176
+ address: data.address,
177
+ chainId: data.chainId,
178
+ authenticatedAt: data.authenticatedAt
179
+ };
180
+ }
181
+ async function logoutFromBillingApi(config) {
182
+ let response;
183
+ try {
184
+ response = await fetch(`${config.baseUrl}/auth/logout`, {
185
+ method: "POST",
186
+ credentials: "include",
187
+ // Include cookies for session management
188
+ headers: {
189
+ "Content-Type": "application/json"
190
+ }
191
+ });
192
+ } catch (error) {
193
+ throw new BillingSessionError(
194
+ `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,
195
+ "NETWORK_ERROR"
196
+ );
197
+ }
198
+ if (response.status === 401) {
199
+ return;
200
+ }
201
+ if (!response.ok) {
202
+ const errorMessage = await parseErrorResponse(response);
203
+ throw new BillingSessionError(`Logout failed: ${errorMessage}`, "UNKNOWN", response.status);
204
+ }
205
+ }
206
+
77
207
  // src/client/common/utils/billingapi.ts
78
208
  var BillingApiClient = class {
79
- constructor(config, walletClient) {
209
+ constructor(config, walletClient, options = {}) {
80
210
  this.config = config;
81
211
  this.walletClient = walletClient;
212
+ this.options = options;
213
+ this.useSession = options.useSession ?? false;
214
+ if (!this.useSession && !walletClient) {
215
+ throw new Error("WalletClient is required when not using session authentication");
216
+ }
82
217
  }
83
218
  /**
84
219
  * Get the address of the connected wallet
220
+ * Returns undefined if using session auth without a wallet client
85
221
  */
86
222
  get address() {
87
- const account = this.walletClient.account;
223
+ const account = this.walletClient?.account;
88
224
  if (!account) {
89
- throw new Error("WalletClient must have an account attached");
225
+ if (!this.useSession) {
226
+ throw new Error("WalletClient must have an account attached");
227
+ }
228
+ return void 0;
90
229
  }
91
230
  return account.address;
92
231
  }
93
- async createSubscription(productId = "compute") {
232
+ /**
233
+ * Get the base URL of the billing API
234
+ */
235
+ get baseUrl() {
236
+ return this.config.billingApiServerURL;
237
+ }
238
+ // ==========================================================================
239
+ // SIWE Session Methods
240
+ // ==========================================================================
241
+ /**
242
+ * Login to the billing API using SIWE
243
+ *
244
+ * This establishes a session with the billing API by verifying the SIWE message
245
+ * and signature. On success, a session cookie is set in the browser.
246
+ *
247
+ * @param request - Login request containing SIWE message and signature
248
+ * @returns Login result with the authenticated address
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * const { message } = createSiweMessage({
253
+ * address: userAddress,
254
+ * chainId: 11155111,
255
+ * domain: window.location.host,
256
+ * uri: window.location.origin,
257
+ * });
258
+ *
259
+ * const signature = await signMessageAsync({ message });
260
+ * const result = await billingClient.siweLogin({ message, signature });
261
+ * ```
262
+ */
263
+ async siweLogin(request) {
264
+ return loginToBillingApi({ baseUrl: this.baseUrl }, request);
265
+ }
266
+ /**
267
+ * Logout from the billing API
268
+ *
269
+ * This destroys the current session and clears the session cookie.
270
+ */
271
+ async siweLogout() {
272
+ return logoutFromBillingApi({ baseUrl: this.baseUrl });
273
+ }
274
+ /**
275
+ * Get the current session status from the billing API
276
+ *
277
+ * @returns Session information including authentication status and address
278
+ */
279
+ async getSession() {
280
+ return getBillingApiSession({ baseUrl: this.baseUrl });
281
+ }
282
+ /**
283
+ * Check if there is a valid session
284
+ *
285
+ * @returns True if session is authenticated, false otherwise
286
+ */
287
+ async isSessionValid() {
288
+ const session = await this.getSession();
289
+ return session.authenticated;
290
+ }
291
+ // ==========================================================================
292
+ // Subscription Methods
293
+ // ==========================================================================
294
+ async createSubscription(productId = "compute", options) {
94
295
  const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
95
- const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId);
296
+ const body = options ? {
297
+ success_url: options.successUrl,
298
+ cancel_url: options.cancelUrl
299
+ } : void 0;
300
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId, body);
96
301
  return resp.json();
97
302
  }
98
303
  async getSubscription(productId = "compute") {
@@ -104,10 +309,72 @@ var BillingApiClient = class {
104
309
  const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
105
310
  await this.makeAuthenticatedRequest(endpoint, "DELETE", productId);
106
311
  }
312
+ // ==========================================================================
313
+ // Internal Methods
314
+ // ==========================================================================
107
315
  /**
108
316
  * Make an authenticated request to the billing API
317
+ *
318
+ * Uses session auth if useSession is true, otherwise uses EIP-712 signature auth.
319
+ */
320
+ async makeAuthenticatedRequest(url, method, productId, body) {
321
+ if (this.useSession) {
322
+ return this.makeSessionAuthenticatedRequest(url, method, body);
323
+ }
324
+ return this.makeSignatureAuthenticatedRequest(url, method, productId, body);
325
+ }
326
+ /**
327
+ * Make a request using session-based authentication (cookies)
328
+ */
329
+ async makeSessionAuthenticatedRequest(url, method, body) {
330
+ const headers = {};
331
+ if (body) {
332
+ headers["Content-Type"] = "application/json";
333
+ }
334
+ try {
335
+ const response = await fetch(url, {
336
+ method,
337
+ credentials: "include",
338
+ // Include cookies for session management
339
+ headers,
340
+ body: body ? JSON.stringify(body) : void 0
341
+ });
342
+ const status = response.status;
343
+ const statusText = status >= 200 && status < 300 ? "OK" : "Error";
344
+ if (status < 200 || status >= 300) {
345
+ let errorBody;
346
+ try {
347
+ errorBody = await response.text();
348
+ } catch {
349
+ errorBody = statusText;
350
+ }
351
+ throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${errorBody}`);
352
+ }
353
+ const responseData = await response.json();
354
+ return {
355
+ json: async () => responseData,
356
+ text: async () => JSON.stringify(responseData)
357
+ };
358
+ } catch (error) {
359
+ if (error.name === "TypeError" || error.message?.includes("fetch")) {
360
+ throw new Error(
361
+ `Failed to connect to BillingAPI at ${url}: ${error.message}
362
+ Please check:
363
+ 1. Your internet connection
364
+ 2. The API server is accessible: ${this.config.billingApiServerURL}
365
+ 3. Firewall/proxy settings`
366
+ );
367
+ }
368
+ throw error;
369
+ }
370
+ }
371
+ /**
372
+ * Make a request using EIP-712 signature authentication
109
373
  */
110
- async makeAuthenticatedRequest(url, method, productId) {
374
+ async makeSignatureAuthenticatedRequest(url, method, productId, body) {
375
+ if (!this.walletClient) {
376
+ throw new Error("WalletClient is required for signature authentication");
377
+ }
111
378
  const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
112
379
  const { signature } = await calculateBillingAuthSignature({
113
380
  walletClient: this.walletClient,
@@ -119,11 +386,15 @@ var BillingApiClient = class {
119
386
  "X-Account": this.address,
120
387
  "X-Expiry": expiry.toString()
121
388
  };
389
+ if (body) {
390
+ headers["Content-Type"] = "application/json";
391
+ }
122
392
  try {
123
393
  const response = await (0, import_axios.default)({
124
394
  method,
125
395
  url,
126
396
  headers,
397
+ data: body,
127
398
  timeout: 3e4,
128
399
  maxRedirects: 0,
129
400
  validateStatus: () => true
@@ -132,8 +403,8 @@ var BillingApiClient = class {
132
403
  const status = response.status;
133
404
  const statusText = status >= 200 && status < 300 ? "OK" : "Error";
134
405
  if (status < 200 || status >= 300) {
135
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
136
- throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body}`);
406
+ const body2 = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
407
+ throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body2}`);
137
408
  }
138
409
  return {
139
410
  json: async () => response.data,
@@ -249,6 +520,9 @@ var import_accounts = require("viem/accounts");
249
520
  // src/client/common/constants.ts
250
521
  var import_chains = require("viem/chains");
251
522
 
523
+ // src/client/common/utils/userapi.ts
524
+ init_session();
525
+
252
526
  // src/client/common/utils/billing.ts
253
527
  function isSubscriptionActive(status) {
254
528
  return status === "active" || status === "trialing";
@@ -489,7 +763,10 @@ function createBillingModule(config) {
489
763
  };
490
764
  }
491
765
  logger.debug(`Creating subscription for ${productId}...`);
492
- const result = await billingApi.createSubscription(productId);
766
+ const result = await billingApi.createSubscription(productId, {
767
+ successUrl: opts?.successUrl,
768
+ cancelUrl: opts?.cancelUrl
769
+ });
493
770
  logger.debug(`Checkout URL: ${result.checkoutUrl}`);
494
771
  return {
495
772
  type: "checkout_created",