@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/dist/billing.js CHANGED
@@ -1,3 +1,15 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
6
+ // src/client/common/auth/session.ts
7
+ var init_session = __esm({
8
+ "src/client/common/auth/session.ts"() {
9
+ "use strict";
10
+ }
11
+ });
12
+
1
13
  // src/client/common/utils/billingapi.ts
2
14
  import axios from "axios";
3
15
 
@@ -38,25 +50,220 @@ async function calculateBillingAuthSignature(options) {
38
50
  return { signature, expiry };
39
51
  }
40
52
 
53
+ // src/client/common/auth/billingSession.ts
54
+ var BillingSessionError = class extends Error {
55
+ constructor(message, code, statusCode) {
56
+ super(message);
57
+ this.code = code;
58
+ this.statusCode = statusCode;
59
+ this.name = "BillingSessionError";
60
+ }
61
+ };
62
+ function stripHexPrefix(hex) {
63
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
64
+ }
65
+ async function parseErrorResponse(response) {
66
+ try {
67
+ const data = await response.json();
68
+ return data.error || response.statusText;
69
+ } catch {
70
+ return response.statusText;
71
+ }
72
+ }
73
+ async function loginToBillingApi(config, request) {
74
+ let response;
75
+ try {
76
+ response = await fetch(`${config.baseUrl}/auth/siwe/login`, {
77
+ method: "POST",
78
+ credentials: "include",
79
+ // Include cookies for session management
80
+ headers: {
81
+ "Content-Type": "application/json"
82
+ },
83
+ body: JSON.stringify({
84
+ message: request.message,
85
+ signature: stripHexPrefix(request.signature)
86
+ })
87
+ });
88
+ } catch (error) {
89
+ throw new BillingSessionError(
90
+ `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,
91
+ "NETWORK_ERROR"
92
+ );
93
+ }
94
+ if (!response.ok) {
95
+ const errorMessage = await parseErrorResponse(response);
96
+ const status = response.status;
97
+ if (status === 400) {
98
+ if (errorMessage.toLowerCase().includes("siwe")) {
99
+ throw new BillingSessionError(`Invalid SIWE message: ${errorMessage}`, "INVALID_MESSAGE", status);
100
+ }
101
+ throw new BillingSessionError(`Bad request: ${errorMessage}`, "INVALID_MESSAGE", status);
102
+ }
103
+ if (status === 401) {
104
+ throw new BillingSessionError(`Invalid signature: ${errorMessage}`, "INVALID_SIGNATURE", status);
105
+ }
106
+ throw new BillingSessionError(`Login failed: ${errorMessage}`, "UNKNOWN", status);
107
+ }
108
+ const data = await response.json();
109
+ return {
110
+ success: data.success,
111
+ address: data.address
112
+ };
113
+ }
114
+ async function getBillingApiSession(config) {
115
+ let response;
116
+ try {
117
+ response = await fetch(`${config.baseUrl}/auth/session`, {
118
+ method: "GET",
119
+ credentials: "include",
120
+ // Include cookies for session management
121
+ headers: {
122
+ "Content-Type": "application/json"
123
+ }
124
+ });
125
+ } catch {
126
+ return {
127
+ authenticated: false
128
+ };
129
+ }
130
+ if (response.status === 401) {
131
+ return {
132
+ authenticated: false
133
+ };
134
+ }
135
+ if (!response.ok) {
136
+ const errorMessage = await parseErrorResponse(response);
137
+ throw new BillingSessionError(`Failed to get session: ${errorMessage}`, "UNKNOWN", response.status);
138
+ }
139
+ const data = await response.json();
140
+ return {
141
+ authenticated: data.authenticated,
142
+ address: data.address,
143
+ chainId: data.chainId,
144
+ authenticatedAt: data.authenticatedAt
145
+ };
146
+ }
147
+ async function logoutFromBillingApi(config) {
148
+ let response;
149
+ try {
150
+ response = await fetch(`${config.baseUrl}/auth/logout`, {
151
+ method: "POST",
152
+ credentials: "include",
153
+ // Include cookies for session management
154
+ headers: {
155
+ "Content-Type": "application/json"
156
+ }
157
+ });
158
+ } catch (error) {
159
+ throw new BillingSessionError(
160
+ `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,
161
+ "NETWORK_ERROR"
162
+ );
163
+ }
164
+ if (response.status === 401) {
165
+ return;
166
+ }
167
+ if (!response.ok) {
168
+ const errorMessage = await parseErrorResponse(response);
169
+ throw new BillingSessionError(`Logout failed: ${errorMessage}`, "UNKNOWN", response.status);
170
+ }
171
+ }
172
+
41
173
  // src/client/common/utils/billingapi.ts
42
174
  var BillingApiClient = class {
43
- constructor(config, walletClient) {
175
+ constructor(config, walletClient, options = {}) {
44
176
  this.config = config;
45
177
  this.walletClient = walletClient;
178
+ this.options = options;
179
+ this.useSession = options.useSession ?? false;
180
+ if (!this.useSession && !walletClient) {
181
+ throw new Error("WalletClient is required when not using session authentication");
182
+ }
46
183
  }
47
184
  /**
48
185
  * Get the address of the connected wallet
186
+ * Returns undefined if using session auth without a wallet client
49
187
  */
50
188
  get address() {
51
- const account = this.walletClient.account;
189
+ const account = this.walletClient?.account;
52
190
  if (!account) {
53
- throw new Error("WalletClient must have an account attached");
191
+ if (!this.useSession) {
192
+ throw new Error("WalletClient must have an account attached");
193
+ }
194
+ return void 0;
54
195
  }
55
196
  return account.address;
56
197
  }
57
- async createSubscription(productId = "compute") {
198
+ /**
199
+ * Get the base URL of the billing API
200
+ */
201
+ get baseUrl() {
202
+ return this.config.billingApiServerURL;
203
+ }
204
+ // ==========================================================================
205
+ // SIWE Session Methods
206
+ // ==========================================================================
207
+ /**
208
+ * Login to the billing API using SIWE
209
+ *
210
+ * This establishes a session with the billing API by verifying the SIWE message
211
+ * and signature. On success, a session cookie is set in the browser.
212
+ *
213
+ * @param request - Login request containing SIWE message and signature
214
+ * @returns Login result with the authenticated address
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const { message } = createSiweMessage({
219
+ * address: userAddress,
220
+ * chainId: 11155111,
221
+ * domain: window.location.host,
222
+ * uri: window.location.origin,
223
+ * });
224
+ *
225
+ * const signature = await signMessageAsync({ message });
226
+ * const result = await billingClient.siweLogin({ message, signature });
227
+ * ```
228
+ */
229
+ async siweLogin(request) {
230
+ return loginToBillingApi({ baseUrl: this.baseUrl }, request);
231
+ }
232
+ /**
233
+ * Logout from the billing API
234
+ *
235
+ * This destroys the current session and clears the session cookie.
236
+ */
237
+ async siweLogout() {
238
+ return logoutFromBillingApi({ baseUrl: this.baseUrl });
239
+ }
240
+ /**
241
+ * Get the current session status from the billing API
242
+ *
243
+ * @returns Session information including authentication status and address
244
+ */
245
+ async getSession() {
246
+ return getBillingApiSession({ baseUrl: this.baseUrl });
247
+ }
248
+ /**
249
+ * Check if there is a valid session
250
+ *
251
+ * @returns True if session is authenticated, false otherwise
252
+ */
253
+ async isSessionValid() {
254
+ const session = await this.getSession();
255
+ return session.authenticated;
256
+ }
257
+ // ==========================================================================
258
+ // Subscription Methods
259
+ // ==========================================================================
260
+ async createSubscription(productId = "compute", options) {
58
261
  const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
59
- const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId);
262
+ const body = options ? {
263
+ success_url: options.successUrl,
264
+ cancel_url: options.cancelUrl
265
+ } : void 0;
266
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId, body);
60
267
  return resp.json();
61
268
  }
62
269
  async getSubscription(productId = "compute") {
@@ -68,10 +275,72 @@ var BillingApiClient = class {
68
275
  const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
69
276
  await this.makeAuthenticatedRequest(endpoint, "DELETE", productId);
70
277
  }
278
+ // ==========================================================================
279
+ // Internal Methods
280
+ // ==========================================================================
71
281
  /**
72
282
  * Make an authenticated request to the billing API
283
+ *
284
+ * Uses session auth if useSession is true, otherwise uses EIP-712 signature auth.
285
+ */
286
+ async makeAuthenticatedRequest(url, method, productId, body) {
287
+ if (this.useSession) {
288
+ return this.makeSessionAuthenticatedRequest(url, method, body);
289
+ }
290
+ return this.makeSignatureAuthenticatedRequest(url, method, productId, body);
291
+ }
292
+ /**
293
+ * Make a request using session-based authentication (cookies)
294
+ */
295
+ async makeSessionAuthenticatedRequest(url, method, body) {
296
+ const headers = {};
297
+ if (body) {
298
+ headers["Content-Type"] = "application/json";
299
+ }
300
+ try {
301
+ const response = await fetch(url, {
302
+ method,
303
+ credentials: "include",
304
+ // Include cookies for session management
305
+ headers,
306
+ body: body ? JSON.stringify(body) : void 0
307
+ });
308
+ const status = response.status;
309
+ const statusText = status >= 200 && status < 300 ? "OK" : "Error";
310
+ if (status < 200 || status >= 300) {
311
+ let errorBody;
312
+ try {
313
+ errorBody = await response.text();
314
+ } catch {
315
+ errorBody = statusText;
316
+ }
317
+ throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${errorBody}`);
318
+ }
319
+ const responseData = await response.json();
320
+ return {
321
+ json: async () => responseData,
322
+ text: async () => JSON.stringify(responseData)
323
+ };
324
+ } catch (error) {
325
+ if (error.name === "TypeError" || error.message?.includes("fetch")) {
326
+ throw new Error(
327
+ `Failed to connect to BillingAPI at ${url}: ${error.message}
328
+ Please check:
329
+ 1. Your internet connection
330
+ 2. The API server is accessible: ${this.config.billingApiServerURL}
331
+ 3. Firewall/proxy settings`
332
+ );
333
+ }
334
+ throw error;
335
+ }
336
+ }
337
+ /**
338
+ * Make a request using EIP-712 signature authentication
73
339
  */
74
- async makeAuthenticatedRequest(url, method, productId) {
340
+ async makeSignatureAuthenticatedRequest(url, method, productId, body) {
341
+ if (!this.walletClient) {
342
+ throw new Error("WalletClient is required for signature authentication");
343
+ }
75
344
  const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
76
345
  const { signature } = await calculateBillingAuthSignature({
77
346
  walletClient: this.walletClient,
@@ -83,11 +352,15 @@ var BillingApiClient = class {
83
352
  "X-Account": this.address,
84
353
  "X-Expiry": expiry.toString()
85
354
  };
355
+ if (body) {
356
+ headers["Content-Type"] = "application/json";
357
+ }
86
358
  try {
87
359
  const response = await axios({
88
360
  method,
89
361
  url,
90
362
  headers,
363
+ data: body,
91
364
  timeout: 3e4,
92
365
  maxRedirects: 0,
93
366
  validateStatus: () => true
@@ -96,8 +369,8 @@ var BillingApiClient = class {
96
369
  const status = response.status;
97
370
  const statusText = status >= 200 && status < 300 ? "OK" : "Error";
98
371
  if (status < 200 || status >= 300) {
99
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
100
- throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body}`);
372
+ const body2 = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
373
+ throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body2}`);
101
374
  }
102
375
  return {
103
376
  json: async () => response.data,
@@ -213,6 +486,9 @@ import { privateKeyToAccount } from "viem/accounts";
213
486
  // src/client/common/constants.ts
214
487
  import { sepolia, mainnet } from "viem/chains";
215
488
 
489
+ // src/client/common/utils/userapi.ts
490
+ init_session();
491
+
216
492
  // src/client/common/utils/billing.ts
217
493
  function isSubscriptionActive(status) {
218
494
  return status === "active" || status === "trialing";
@@ -453,7 +729,10 @@ function createBillingModule(config) {
453
729
  };
454
730
  }
455
731
  logger.debug(`Creating subscription for ${productId}...`);
456
- const result = await billingApi.createSubscription(productId);
732
+ const result = await billingApi.createSubscription(productId, {
733
+ successUrl: opts?.successUrl,
734
+ cancelUrl: opts?.cancelUrl
735
+ });
457
736
  logger.debug(`Checkout URL: ${result.checkoutUrl}`);
458
737
  return {
459
738
  type: "checkout_created",