opencloud-platform-sdk 1.3.0 → 2.0.0
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 +120 -156
- package/dist/index.d.mts +78 -51
- package/dist/index.d.ts +78 -51
- package/dist/index.js +186 -245
- package/dist/index.mjs +186 -245
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,65 +25,31 @@ __export(index_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
28
|
-
var STORAGE_KEYS = {
|
|
29
|
-
OPENROUTER_KEY: "openrouter_api_key",
|
|
30
|
-
ANTHROPIC_KEY: "anthropic_api_key",
|
|
31
|
-
OPENAI_KEY: "openai_api_key",
|
|
32
|
-
PREVIEW_PROVIDER: "opencloud_preview_provider"
|
|
33
|
-
};
|
|
34
|
-
function getCookie(name) {
|
|
35
|
-
if (!isBrowser) return null;
|
|
36
|
-
const value = `; ${document.cookie}`;
|
|
37
|
-
const parts = value.split(`; ${name}=`);
|
|
38
|
-
if (parts.length === 2) {
|
|
39
|
-
const cookieValue = parts.pop()?.split(";").shift();
|
|
40
|
-
if (cookieValue) {
|
|
41
|
-
try {
|
|
42
|
-
return decodeURIComponent(cookieValue);
|
|
43
|
-
} catch {
|
|
44
|
-
return cookieValue;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
function getBoltApiKeys() {
|
|
51
|
-
const apiKeysCookie = getCookie("apiKeys");
|
|
52
|
-
if (!apiKeysCookie) return null;
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(apiKeysCookie);
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
28
|
var OpenCloudSDK = class {
|
|
60
29
|
constructor() {
|
|
61
30
|
this._isPreviewMode = false;
|
|
31
|
+
this._cachedSession = null;
|
|
62
32
|
this.config = {
|
|
63
33
|
appId: "",
|
|
64
|
-
apiUrl: isBrowser ? window.location.origin : "
|
|
65
|
-
previewApiKey: void 0
|
|
34
|
+
apiUrl: isBrowser ? window.location.origin : "https://opencloud.app"
|
|
66
35
|
};
|
|
67
36
|
if (isBrowser) {
|
|
68
37
|
this._isPreviewMode = this.detectPreviewMode();
|
|
69
38
|
}
|
|
70
39
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Detect if running in preview/development mode
|
|
73
|
-
*/
|
|
74
40
|
detectPreviewMode() {
|
|
75
41
|
if (!isBrowser) return false;
|
|
76
42
|
const hostname = window.location.hostname;
|
|
77
43
|
return hostname.includes("webcontainer") || hostname.includes("localhost") || hostname.includes("127.0.0.1") || hostname.includes(".local") || hostname.includes("stackblitz") || hostname.includes("codesandbox");
|
|
78
44
|
}
|
|
79
45
|
/**
|
|
80
|
-
* Check if
|
|
46
|
+
* Check if running in preview/development mode
|
|
81
47
|
*/
|
|
82
48
|
isPreview() {
|
|
83
49
|
return this._isPreviewMode;
|
|
84
50
|
}
|
|
85
51
|
/**
|
|
86
|
-
* Initialize the SDK
|
|
52
|
+
* Initialize the SDK with your app ID
|
|
87
53
|
*/
|
|
88
54
|
init(options) {
|
|
89
55
|
if (!options.appId) {
|
|
@@ -91,8 +57,7 @@ var OpenCloudSDK = class {
|
|
|
91
57
|
}
|
|
92
58
|
this.config = {
|
|
93
59
|
appId: options.appId,
|
|
94
|
-
apiUrl: options.apiUrl || this.config.apiUrl
|
|
95
|
-
previewApiKey: options.previewApiKey
|
|
60
|
+
apiUrl: options.apiUrl || this.config.apiUrl
|
|
96
61
|
};
|
|
97
62
|
if (!this._isPreviewMode) {
|
|
98
63
|
this.startHeartbeat();
|
|
@@ -101,235 +66,105 @@ var OpenCloudSDK = class {
|
|
|
101
66
|
console.log(`[OpenCloudSDK] Initialized for app: ${options.appId} (${mode} mode)`);
|
|
102
67
|
}
|
|
103
68
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
69
|
+
* Track a usage event and charge the user
|
|
70
|
+
* Call this BEFORE or AFTER your AI/API call
|
|
71
|
+
*
|
|
72
|
+
* Example:
|
|
73
|
+
* ```
|
|
74
|
+
* // Check if user can afford it first
|
|
75
|
+
* const canUse = await opencloud.canAfford();
|
|
76
|
+
* if (!canUse) {
|
|
77
|
+
* opencloud.requestTopUp();
|
|
78
|
+
* return;
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* // Do your AI call
|
|
82
|
+
* const result = await myAICall();
|
|
83
|
+
*
|
|
84
|
+
* // Track the usage (charges user)
|
|
85
|
+
* await opencloud.trackUsage({ action: 'chat_message' });
|
|
86
|
+
* ```
|
|
106
87
|
*/
|
|
107
|
-
|
|
108
|
-
if (!isBrowser) return;
|
|
109
|
-
const storageKey = provider === "openrouter" ? STORAGE_KEYS.OPENROUTER_KEY : provider === "anthropic" ? STORAGE_KEYS.ANTHROPIC_KEY : STORAGE_KEYS.OPENAI_KEY;
|
|
110
|
-
localStorage.setItem(storageKey, key);
|
|
111
|
-
localStorage.setItem(STORAGE_KEYS.PREVIEW_PROVIDER, provider);
|
|
112
|
-
this.config.previewApiKey = key;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Get API key for preview mode
|
|
116
|
-
* Checks in order: config, localStorage, bolt.diy cookies
|
|
117
|
-
*/
|
|
118
|
-
getPreviewApiKey() {
|
|
119
|
-
if (!isBrowser) return null;
|
|
120
|
-
if (this.config.previewApiKey) {
|
|
121
|
-
return { key: this.config.previewApiKey, provider: "openrouter" };
|
|
122
|
-
}
|
|
123
|
-
const provider = localStorage.getItem(STORAGE_KEYS.PREVIEW_PROVIDER) || "openrouter";
|
|
124
|
-
const storageKey = provider === "openrouter" ? STORAGE_KEYS.OPENROUTER_KEY : provider === "anthropic" ? STORAGE_KEYS.ANTHROPIC_KEY : STORAGE_KEYS.OPENAI_KEY;
|
|
125
|
-
const key = localStorage.getItem(storageKey);
|
|
126
|
-
if (key) {
|
|
127
|
-
return { key, provider };
|
|
128
|
-
}
|
|
129
|
-
for (const [name, storageKey2] of Object.entries(STORAGE_KEYS)) {
|
|
130
|
-
if (name === "PREVIEW_PROVIDER") continue;
|
|
131
|
-
const key2 = localStorage.getItem(storageKey2);
|
|
132
|
-
if (key2) {
|
|
133
|
-
return { key: key2, provider: name.replace("_KEY", "").toLowerCase() };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const boltApiKeys = getBoltApiKeys();
|
|
137
|
-
if (boltApiKeys) {
|
|
138
|
-
const providerMappings = [
|
|
139
|
-
{ boltName: "OpenRouter", provider: "openrouter" },
|
|
140
|
-
{ boltName: "Anthropic", provider: "anthropic" },
|
|
141
|
-
{ boltName: "OpenAI", provider: "openai" },
|
|
142
|
-
// Also check with _API_KEY suffix (older format)
|
|
143
|
-
{ boltName: "OpenRouter_API_KEY", provider: "openrouter" },
|
|
144
|
-
{ boltName: "Anthropic_API_KEY", provider: "anthropic" },
|
|
145
|
-
{ boltName: "OpenAI_API_KEY", provider: "openai" }
|
|
146
|
-
];
|
|
147
|
-
for (const { boltName, provider: provider2 } of providerMappings) {
|
|
148
|
-
const apiKey = boltApiKeys[boltName];
|
|
149
|
-
if (apiKey && apiKey.length > 10 && !apiKey.includes("your_")) {
|
|
150
|
-
console.log(`[OpenCloudSDK] Using API key from bolt.diy (${provider2})`);
|
|
151
|
-
return { key: apiKey, provider: provider2 };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Call an AI model - automatically uses preview or production mode
|
|
159
|
-
*/
|
|
160
|
-
async callAI(params) {
|
|
88
|
+
async trackUsage(params = {}) {
|
|
161
89
|
if (!this.config.appId) {
|
|
162
|
-
throw new Error("OpenCloudSDK: Must call init() before
|
|
163
|
-
}
|
|
164
|
-
const { model, prompt, options = {} } = params;
|
|
165
|
-
if (!model || !prompt) {
|
|
166
|
-
throw new Error("OpenCloudSDK: model and prompt are required");
|
|
90
|
+
throw new Error("OpenCloudSDK: Must call init() before tracking usage");
|
|
167
91
|
}
|
|
168
92
|
if (this._isPreviewMode) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*/
|
|
177
|
-
async callAIPreview(params) {
|
|
178
|
-
const { model, prompt, options = {} } = params;
|
|
179
|
-
const apiKeyInfo = this.getPreviewApiKey();
|
|
180
|
-
if (!apiKeyInfo) {
|
|
181
|
-
throw new Error(
|
|
182
|
-
'[OpenCloudSDK Preview] No API key found. Please set your API key using opencloud.setPreviewApiKey("your-key") or configure it in the builder settings.'
|
|
183
|
-
);
|
|
93
|
+
console.log("[OpenCloudSDK Preview] Usage tracked (no charge in preview mode)");
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
charged: 0,
|
|
97
|
+
userBalance: 999.99,
|
|
98
|
+
transactionId: "preview-" + Date.now()
|
|
99
|
+
};
|
|
184
100
|
}
|
|
185
|
-
const { key, provider } = apiKeyInfo;
|
|
186
101
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
responseData = await response.json();
|
|
205
|
-
if (!response.ok) {
|
|
206
|
-
throw new Error(responseData.error?.message || `OpenRouter API error: ${response.status}`);
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
success: true,
|
|
210
|
-
text: responseData.choices?.[0]?.message?.content || "",
|
|
211
|
-
usage: {
|
|
212
|
-
promptTokens: responseData.usage?.prompt_tokens || 0,
|
|
213
|
-
completionTokens: responseData.usage?.completion_tokens || 0,
|
|
214
|
-
totalTokens: responseData.usage?.total_tokens || 0
|
|
215
|
-
},
|
|
216
|
-
cost: 0,
|
|
217
|
-
// Preview mode - no cost tracking
|
|
218
|
-
model,
|
|
219
|
-
mode: "preview"
|
|
220
|
-
};
|
|
221
|
-
} else if (provider === "anthropic" || provider === "anthropic_key") {
|
|
222
|
-
response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
223
|
-
method: "POST",
|
|
224
|
-
headers: {
|
|
225
|
-
"Content-Type": "application/json",
|
|
226
|
-
"x-api-key": key,
|
|
227
|
-
"anthropic-version": "2023-06-01",
|
|
228
|
-
"anthropic-dangerous-direct-browser-access": "true"
|
|
229
|
-
},
|
|
230
|
-
body: JSON.stringify({
|
|
231
|
-
model: model.replace("anthropic/", ""),
|
|
232
|
-
max_tokens: options.max_tokens || 1024,
|
|
233
|
-
messages: [{ role: "user", content: prompt }]
|
|
234
|
-
})
|
|
235
|
-
});
|
|
236
|
-
responseData = await response.json();
|
|
237
|
-
if (!response.ok) {
|
|
238
|
-
throw new Error(responseData.error?.message || `Anthropic API error: ${response.status}`);
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
success: true,
|
|
242
|
-
text: responseData.content?.[0]?.text || "",
|
|
243
|
-
usage: {
|
|
244
|
-
promptTokens: responseData.usage?.input_tokens || 0,
|
|
245
|
-
completionTokens: responseData.usage?.output_tokens || 0,
|
|
246
|
-
totalTokens: (responseData.usage?.input_tokens || 0) + (responseData.usage?.output_tokens || 0)
|
|
247
|
-
},
|
|
248
|
-
cost: 0,
|
|
249
|
-
model,
|
|
250
|
-
mode: "preview"
|
|
251
|
-
};
|
|
252
|
-
} else if (provider === "openai" || provider === "openai_key") {
|
|
253
|
-
response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
254
|
-
method: "POST",
|
|
255
|
-
headers: {
|
|
256
|
-
"Content-Type": "application/json",
|
|
257
|
-
"Authorization": `Bearer ${key}`
|
|
258
|
-
},
|
|
259
|
-
body: JSON.stringify({
|
|
260
|
-
model: model.replace("openai/", ""),
|
|
261
|
-
messages: [{ role: "user", content: prompt }],
|
|
262
|
-
...options
|
|
263
|
-
})
|
|
264
|
-
});
|
|
265
|
-
responseData = await response.json();
|
|
266
|
-
if (!response.ok) {
|
|
267
|
-
throw new Error(responseData.error?.message || `OpenAI API error: ${response.status}`);
|
|
102
|
+
const session = await this.getUserSession();
|
|
103
|
+
const response = await fetch(`${this.config.apiUrl}/api/sdk/track-usage`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
"Authorization": `Bearer ${session.token}`
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
appId: this.config.appId,
|
|
111
|
+
action: params.action,
|
|
112
|
+
metadata: params.metadata
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const error = await response.json();
|
|
117
|
+
if (response.status === 402) {
|
|
118
|
+
throw new Error("INSUFFICIENT_BALANCE");
|
|
268
119
|
}
|
|
269
|
-
|
|
270
|
-
success: true,
|
|
271
|
-
text: responseData.choices?.[0]?.message?.content || "",
|
|
272
|
-
usage: {
|
|
273
|
-
promptTokens: responseData.usage?.prompt_tokens || 0,
|
|
274
|
-
completionTokens: responseData.usage?.completion_tokens || 0,
|
|
275
|
-
totalTokens: responseData.usage?.total_tokens || 0
|
|
276
|
-
},
|
|
277
|
-
cost: 0,
|
|
278
|
-
model,
|
|
279
|
-
mode: "preview"
|
|
280
|
-
};
|
|
120
|
+
throw new Error(error.error || `Failed to track usage: ${response.status}`);
|
|
281
121
|
}
|
|
282
|
-
|
|
122
|
+
return await response.json();
|
|
283
123
|
} catch (error) {
|
|
284
|
-
console.error("[OpenCloudSDK
|
|
124
|
+
console.error("[OpenCloudSDK] Track usage failed:", error);
|
|
285
125
|
throw error;
|
|
286
126
|
}
|
|
287
127
|
}
|
|
288
128
|
/**
|
|
289
|
-
*
|
|
129
|
+
* Check if the user can afford to use the app
|
|
130
|
+
* Returns true if user has enough balance
|
|
290
131
|
*/
|
|
291
|
-
async
|
|
292
|
-
|
|
132
|
+
async canAfford() {
|
|
133
|
+
if (this._isPreviewMode) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
293
136
|
try {
|
|
294
137
|
const session = await this.getUserSession();
|
|
295
|
-
const response = await fetch(`${this.config.apiUrl}/api/
|
|
138
|
+
const response = await fetch(`${this.config.apiUrl}/api/sdk/can-afford`, {
|
|
296
139
|
method: "POST",
|
|
297
140
|
headers: {
|
|
298
141
|
"Content-Type": "application/json",
|
|
299
142
|
"Authorization": `Bearer ${session.token}`
|
|
300
143
|
},
|
|
301
144
|
body: JSON.stringify({
|
|
302
|
-
appId: this.config.appId
|
|
303
|
-
model,
|
|
304
|
-
prompt,
|
|
305
|
-
options
|
|
145
|
+
appId: this.config.appId
|
|
306
146
|
})
|
|
307
147
|
});
|
|
308
148
|
if (!response.ok) {
|
|
309
|
-
|
|
310
|
-
throw new Error(error.error || `API call failed: ${response.status}`);
|
|
149
|
+
return false;
|
|
311
150
|
}
|
|
312
151
|
const data = await response.json();
|
|
313
|
-
return
|
|
314
|
-
...data,
|
|
315
|
-
mode: "production"
|
|
316
|
-
};
|
|
152
|
+
return data.canAfford;
|
|
317
153
|
} catch (error) {
|
|
318
|
-
console.error("[OpenCloudSDK
|
|
319
|
-
|
|
154
|
+
console.error("[OpenCloudSDK] canAfford check failed:", error);
|
|
155
|
+
return false;
|
|
320
156
|
}
|
|
321
157
|
}
|
|
322
158
|
/**
|
|
323
|
-
* Get current user's balance
|
|
159
|
+
* Get current user's balance
|
|
324
160
|
*/
|
|
325
161
|
async getBalance() {
|
|
326
162
|
if (this._isPreviewMode) {
|
|
327
|
-
|
|
328
|
-
return 0;
|
|
163
|
+
return 999.99;
|
|
329
164
|
}
|
|
330
165
|
try {
|
|
331
166
|
const session = await this.getUserSession();
|
|
332
|
-
const response = await fetch(`${this.config.apiUrl}/api/
|
|
167
|
+
const response = await fetch(`${this.config.apiUrl}/api/sdk/balance`, {
|
|
333
168
|
headers: {
|
|
334
169
|
"Authorization": `Bearer ${session.token}`
|
|
335
170
|
}
|
|
@@ -345,9 +180,54 @@ var OpenCloudSDK = class {
|
|
|
345
180
|
}
|
|
346
181
|
}
|
|
347
182
|
/**
|
|
348
|
-
*
|
|
183
|
+
* Get current user info
|
|
184
|
+
*/
|
|
185
|
+
async getUserInfo() {
|
|
186
|
+
if (this._isPreviewMode) {
|
|
187
|
+
return {
|
|
188
|
+
id: "preview-user",
|
|
189
|
+
email: "preview@example.com",
|
|
190
|
+
balance: 999.99,
|
|
191
|
+
username: "preview_user"
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const session = await this.getUserSession();
|
|
196
|
+
const response = await fetch(`${this.config.apiUrl}/api/sdk/user`, {
|
|
197
|
+
headers: {
|
|
198
|
+
"Authorization": `Bearer ${session.token}`
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
throw new Error("Failed to get user info");
|
|
203
|
+
}
|
|
204
|
+
return await response.json();
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error("[OpenCloudSDK] Get user info failed:", error);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the price per use for this app
|
|
212
|
+
*/
|
|
213
|
+
async getAppPrice() {
|
|
214
|
+
try {
|
|
215
|
+
const response = await fetch(`${this.config.apiUrl}/api/sdk/app-price?appId=${this.config.appId}`);
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new Error("Failed to get app price");
|
|
218
|
+
}
|
|
219
|
+
const data = await response.json();
|
|
220
|
+
return data.pricePerUse;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error("[OpenCloudSDK] Get app price failed:", error);
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Request user to add more credits
|
|
228
|
+
* Opens the top-up modal or redirects to payment page
|
|
349
229
|
*/
|
|
350
|
-
|
|
230
|
+
requestTopUp() {
|
|
351
231
|
if (!isBrowser) return;
|
|
352
232
|
if (this._isPreviewMode) {
|
|
353
233
|
console.warn("[OpenCloudSDK] Top-up not available in preview mode");
|
|
@@ -359,45 +239,72 @@ var OpenCloudSDK = class {
|
|
|
359
239
|
appId: this.config.appId
|
|
360
240
|
}, "*");
|
|
361
241
|
} else {
|
|
362
|
-
window.location.href = `${this.config.apiUrl}/
|
|
242
|
+
window.location.href = `${this.config.apiUrl}/settings/credits`;
|
|
363
243
|
}
|
|
364
244
|
}
|
|
365
245
|
/**
|
|
366
|
-
*
|
|
246
|
+
* Check if user is authenticated
|
|
367
247
|
*/
|
|
248
|
+
async isAuthenticated() {
|
|
249
|
+
if (this._isPreviewMode) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
await this.getUserSession();
|
|
254
|
+
return true;
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Request user to log in
|
|
261
|
+
*/
|
|
262
|
+
requestLogin() {
|
|
263
|
+
if (!isBrowser) return;
|
|
264
|
+
if (this._isPreviewMode) {
|
|
265
|
+
console.warn("[OpenCloudSDK] Login not required in preview mode");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (window.parent !== window) {
|
|
269
|
+
window.parent.postMessage({
|
|
270
|
+
type: "OPENCLOUD_REQUEST_LOGIN",
|
|
271
|
+
appId: this.config.appId
|
|
272
|
+
}, "*");
|
|
273
|
+
} else {
|
|
274
|
+
window.location.href = `${this.config.apiUrl}/auth/login?redirect=${encodeURIComponent(window.location.href)}`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
368
277
|
startHeartbeat() {
|
|
369
278
|
if (!isBrowser) return;
|
|
370
279
|
this.sendHeartbeat();
|
|
371
280
|
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 3e4);
|
|
372
281
|
}
|
|
373
|
-
/**
|
|
374
|
-
* Send heartbeat to platform
|
|
375
|
-
*/
|
|
376
282
|
sendHeartbeat() {
|
|
377
283
|
if (!this.config.appId || !isBrowser) return;
|
|
378
284
|
const data = JSON.stringify({
|
|
379
285
|
appId: this.config.appId,
|
|
380
286
|
timestamp: Date.now(),
|
|
381
|
-
version: "
|
|
287
|
+
version: "2.0.0",
|
|
382
288
|
url: window.location.href
|
|
383
289
|
});
|
|
384
290
|
if (navigator.sendBeacon) {
|
|
385
291
|
navigator.sendBeacon(`${this.config.apiUrl}/api/sdk/heartbeat`, data);
|
|
386
292
|
}
|
|
387
293
|
}
|
|
388
|
-
/**
|
|
389
|
-
* Get user session token
|
|
390
|
-
*/
|
|
391
294
|
async getUserSession() {
|
|
392
295
|
if (!isBrowser) {
|
|
393
296
|
throw new Error("getUserSession can only be called in browser");
|
|
394
297
|
}
|
|
298
|
+
if (this._cachedSession) {
|
|
299
|
+
return this._cachedSession;
|
|
300
|
+
}
|
|
395
301
|
return new Promise((resolve, reject) => {
|
|
396
302
|
if (window.parent !== window) {
|
|
397
303
|
const messageHandler = (event) => {
|
|
398
304
|
if (event.data.type === "USER_SESSION_RESPONSE") {
|
|
399
305
|
window.removeEventListener("message", messageHandler);
|
|
400
306
|
if (event.data.session) {
|
|
307
|
+
this._cachedSession = event.data.session;
|
|
401
308
|
resolve(event.data.session);
|
|
402
309
|
} else {
|
|
403
310
|
reject(new Error("User not authenticated"));
|
|
@@ -416,6 +323,7 @@ var OpenCloudSDK = class {
|
|
|
416
323
|
} else {
|
|
417
324
|
const token = localStorage.getItem("opencloud_session_token");
|
|
418
325
|
if (token) {
|
|
326
|
+
this._cachedSession = { token };
|
|
419
327
|
resolve({ token });
|
|
420
328
|
} else {
|
|
421
329
|
reject(new Error("User not authenticated. Please log in."));
|
|
@@ -424,18 +332,51 @@ var OpenCloudSDK = class {
|
|
|
424
332
|
});
|
|
425
333
|
}
|
|
426
334
|
/**
|
|
427
|
-
*
|
|
335
|
+
* Clear cached session (call on logout)
|
|
336
|
+
*/
|
|
337
|
+
clearSession() {
|
|
338
|
+
this._cachedSession = null;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Cleanup resources
|
|
428
342
|
*/
|
|
429
343
|
destroy() {
|
|
430
344
|
if (this.heartbeatInterval) {
|
|
431
345
|
clearInterval(this.heartbeatInterval);
|
|
432
346
|
}
|
|
347
|
+
this._cachedSession = null;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Execute a function and automatically charge the user
|
|
351
|
+
* This is a convenience method that combines canAfford + your code + trackUsage
|
|
352
|
+
*
|
|
353
|
+
* @param fn - The async function to execute (your AI call, etc.)
|
|
354
|
+
* @param options - Optional tracking options
|
|
355
|
+
* @returns The result of your function
|
|
356
|
+
*
|
|
357
|
+
* Example:
|
|
358
|
+
* ```typescript
|
|
359
|
+
* const result = await opencloud.withUsage(async () => {
|
|
360
|
+
* const response = await fetch('https://api.openai.com/...', { ... });
|
|
361
|
+
* return await response.json();
|
|
362
|
+
* }, { action: 'chat_message' });
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
async withUsage(fn, options = {}) {
|
|
366
|
+
const canUse = await this.canAfford();
|
|
367
|
+
if (!canUse) {
|
|
368
|
+
this.requestTopUp();
|
|
369
|
+
throw new Error("INSUFFICIENT_BALANCE");
|
|
370
|
+
}
|
|
371
|
+
const result = await fn();
|
|
372
|
+
await this.trackUsage(options);
|
|
373
|
+
return result;
|
|
433
374
|
}
|
|
434
375
|
};
|
|
435
376
|
var opencloud = new OpenCloudSDK();
|
|
436
377
|
if (isBrowser) {
|
|
437
378
|
window.OpenCloudSDK = opencloud;
|
|
438
|
-
window.
|
|
379
|
+
window.opencloud = opencloud;
|
|
439
380
|
}
|
|
440
381
|
// Annotate the CommonJS export names for ESM import in node:
|
|
441
382
|
0 && (module.exports = {
|