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