vibeusage 0.3.0 → 0.3.2
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 +19 -11
- package/README.zh-CN.md +10 -8
- package/node_modules/@insforge/sdk/LICENSE +201 -201
- package/node_modules/@insforge/sdk/README.md +326 -259
- package/node_modules/@insforge/sdk/dist/index.d.mts +377 -182
- package/node_modules/@insforge/sdk/dist/index.d.ts +377 -182
- package/node_modules/@insforge/sdk/dist/index.js +1172 -677
- package/node_modules/@insforge/sdk/dist/index.js.map +1 -1
- package/node_modules/@insforge/sdk/dist/index.mjs +1171 -677
- package/node_modules/@insforge/sdk/dist/index.mjs.map +1 -1
- package/node_modules/@insforge/sdk/package.json +68 -68
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.d.ts +1120 -43
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.js +179 -5
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.d.ts +25 -25
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.js +2 -2
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.d.ts +197 -51
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.js +87 -23
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.d.ts +32 -3
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.js +21 -3
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.d.ts +380 -0
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.js +74 -0
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/database-api.schema.d.ts +13 -13
- package/node_modules/@insforge/shared-schemas/dist/database-api.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/database-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.d.ts +735 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.js +209 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.d.ts +37 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.js +25 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.d.ts +5 -1
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.js +34 -4
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/email-api.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/email-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.d.ts +186 -6
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.js +21 -2
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions.schema.d.ts +5 -5
- package/node_modules/@insforge/shared-schemas/dist/functions.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/index.d.ts +24 -18
- package/node_modules/@insforge/shared-schemas/dist/index.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/index.js +24 -18
- package/node_modules/@insforge/shared-schemas/dist/index.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs-api.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.d.ts +43 -0
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.js +11 -0
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.d.ts +229 -172
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.js +27 -7
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.d.ts +51 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.js +31 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.d.ts +31 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.js +12 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.d.ts +39 -20
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.js +5 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.d.ts +12 -4
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.js +6 -0
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.d.ts +287 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.js +81 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.d.ts +77 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.js +36 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.d.ts +113 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.js +31 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.d.ts +31 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.js +13 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.d.ts +27 -2
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.js +9 -1
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.d.ts +17 -0
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.js +6 -0
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/package.json +2 -1
- package/package.json +2 -2
- package/src/commands/status.js +22 -5
- package/src/commands/sync.js +100 -197
- package/src/commands/uninstall.js +0 -11
- package/src/lib/diagnostics.js +34 -9
- package/src/lib/doctor.js +24 -15
- package/src/lib/insforge-client.js +13 -9
- package/src/lib/integrations/context.js +0 -6
- package/src/lib/integrations/index.js +0 -2
- package/src/lib/openclaw-session-plugin.js +48 -138
- package/src/lib/openclaw-usage-ledger.js +237 -0
- package/src/lib/opencode-sqlite.js +113 -0
- package/src/lib/opencode-usage-audit.js +3 -2
- package/src/lib/rollout.js +229 -153
- package/src/lib/vibeusage-api.js +2 -2
- package/src/lib/integrations/openclaw-legacy.js +0 -123
- package/src/lib/openclaw-hook.js +0 -420
|
@@ -17,127 +17,182 @@ var InsForgeError = class _InsForgeError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
// src/lib/
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
// src/lib/logger.ts
|
|
21
|
+
var SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie", "set-cookie"];
|
|
22
|
+
var SENSITIVE_BODY_KEYS = [
|
|
23
|
+
"password",
|
|
24
|
+
"token",
|
|
25
|
+
"accesstoken",
|
|
26
|
+
"refreshtoken",
|
|
27
|
+
"authorization",
|
|
28
|
+
"secret",
|
|
29
|
+
"apikey",
|
|
30
|
+
"api_key",
|
|
31
|
+
"email",
|
|
32
|
+
"ssn",
|
|
33
|
+
"creditcard",
|
|
34
|
+
"credit_card"
|
|
35
|
+
];
|
|
36
|
+
function redactHeaders(headers) {
|
|
37
|
+
const redacted = {};
|
|
38
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
39
|
+
if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
|
|
40
|
+
redacted[key] = "***REDACTED***";
|
|
41
|
+
} else {
|
|
42
|
+
redacted[key] = value;
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
});
|
|
45
|
+
return redacted;
|
|
46
|
+
}
|
|
47
|
+
function sanitizeBody(body) {
|
|
48
|
+
if (body === null || body === void 0) return body;
|
|
49
|
+
if (typeof body === "string") {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(body);
|
|
52
|
+
return sanitizeBody(parsed);
|
|
53
|
+
} catch {
|
|
54
|
+
return body;
|
|
48
55
|
}
|
|
49
|
-
return url.toString();
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const authToken = this.userToken || this.anonKey;
|
|
58
|
-
if (authToken) {
|
|
59
|
-
requestHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
60
|
-
}
|
|
61
|
-
let processedBody;
|
|
62
|
-
if (body !== void 0) {
|
|
63
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
64
|
-
processedBody = body;
|
|
57
|
+
if (Array.isArray(body)) return body.map(sanitizeBody);
|
|
58
|
+
if (typeof body === "object") {
|
|
59
|
+
const sanitized = {};
|
|
60
|
+
for (const [key, value] of Object.entries(body)) {
|
|
61
|
+
if (SENSITIVE_BODY_KEYS.includes(key.toLowerCase().replace(/[-_]/g, ""))) {
|
|
62
|
+
sanitized[key] = "***REDACTED***";
|
|
65
63
|
} else {
|
|
66
|
-
|
|
67
|
-
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
68
|
-
}
|
|
69
|
-
processedBody = JSON.stringify(body);
|
|
64
|
+
sanitized[key] = sanitizeBody(value);
|
|
70
65
|
}
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const contentType = response.headers.get("content-type");
|
|
84
|
-
if (contentType?.includes("json")) {
|
|
85
|
-
data = await response.json();
|
|
86
|
-
} else {
|
|
87
|
-
data = await response.text();
|
|
88
|
-
}
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
if (data && typeof data === "object" && "error" in data) {
|
|
91
|
-
if (!data.statusCode && !data.status) {
|
|
92
|
-
data.statusCode = response.status;
|
|
93
|
-
}
|
|
94
|
-
const error = InsForgeError.fromApiError(data);
|
|
95
|
-
Object.keys(data).forEach((key) => {
|
|
96
|
-
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
97
|
-
error[key] = data[key];
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
throw new InsForgeError(
|
|
103
|
-
`Request failed: ${response.statusText}`,
|
|
104
|
-
response.status,
|
|
105
|
-
"REQUEST_FAILED"
|
|
106
|
-
);
|
|
67
|
+
return sanitized;
|
|
68
|
+
}
|
|
69
|
+
return body;
|
|
70
|
+
}
|
|
71
|
+
function formatBody(body) {
|
|
72
|
+
if (body === void 0 || body === null) return "";
|
|
73
|
+
if (typeof body === "string") {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.stringify(JSON.parse(body), null, 2);
|
|
76
|
+
} catch {
|
|
77
|
+
return body;
|
|
107
78
|
}
|
|
108
|
-
return data;
|
|
109
79
|
}
|
|
110
|
-
|
|
111
|
-
return
|
|
80
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
81
|
+
return "[FormData]";
|
|
112
82
|
}
|
|
113
|
-
|
|
114
|
-
return
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(body, null, 2);
|
|
85
|
+
} catch {
|
|
86
|
+
return "[Unserializable body]";
|
|
115
87
|
}
|
|
116
|
-
|
|
117
|
-
|
|
88
|
+
}
|
|
89
|
+
var Logger = class {
|
|
90
|
+
/**
|
|
91
|
+
* Creates a new Logger instance.
|
|
92
|
+
* @param debug - Set to true to enable console logging, or pass a custom log function
|
|
93
|
+
*/
|
|
94
|
+
constructor(debug) {
|
|
95
|
+
if (typeof debug === "function") {
|
|
96
|
+
this.enabled = true;
|
|
97
|
+
this.customLog = debug;
|
|
98
|
+
} else {
|
|
99
|
+
this.enabled = !!debug;
|
|
100
|
+
this.customLog = null;
|
|
101
|
+
}
|
|
118
102
|
}
|
|
119
|
-
|
|
120
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Logs a debug message at the info level.
|
|
105
|
+
* @param message - The message to log
|
|
106
|
+
* @param args - Additional arguments to pass to the log function
|
|
107
|
+
*/
|
|
108
|
+
log(message, ...args) {
|
|
109
|
+
if (!this.enabled) return;
|
|
110
|
+
const formatted = `[InsForge Debug] ${message}`;
|
|
111
|
+
if (this.customLog) {
|
|
112
|
+
this.customLog(formatted, ...args);
|
|
113
|
+
} else {
|
|
114
|
+
console.log(formatted, ...args);
|
|
115
|
+
}
|
|
121
116
|
}
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Logs a debug message at the warning level.
|
|
119
|
+
* @param message - The message to log
|
|
120
|
+
* @param args - Additional arguments to pass to the log function
|
|
121
|
+
*/
|
|
122
|
+
warn(message, ...args) {
|
|
123
|
+
if (!this.enabled) return;
|
|
124
|
+
const formatted = `[InsForge Debug] ${message}`;
|
|
125
|
+
if (this.customLog) {
|
|
126
|
+
this.customLog(formatted, ...args);
|
|
127
|
+
} else {
|
|
128
|
+
console.warn(formatted, ...args);
|
|
129
|
+
}
|
|
124
130
|
}
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Logs a debug message at the error level.
|
|
133
|
+
* @param message - The message to log
|
|
134
|
+
* @param args - Additional arguments to pass to the log function
|
|
135
|
+
*/
|
|
136
|
+
error(message, ...args) {
|
|
137
|
+
if (!this.enabled) return;
|
|
138
|
+
const formatted = `[InsForge Debug] ${message}`;
|
|
139
|
+
if (this.customLog) {
|
|
140
|
+
this.customLog(formatted, ...args);
|
|
141
|
+
} else {
|
|
142
|
+
console.error(formatted, ...args);
|
|
143
|
+
}
|
|
127
144
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Logs an outgoing HTTP request with method, URL, headers, and body.
|
|
147
|
+
* Sensitive headers and body fields are automatically redacted.
|
|
148
|
+
* @param method - HTTP method (GET, POST, etc.)
|
|
149
|
+
* @param url - The full request URL
|
|
150
|
+
* @param headers - Request headers (sensitive values will be redacted)
|
|
151
|
+
* @param body - Request body (sensitive fields will be masked)
|
|
152
|
+
*/
|
|
153
|
+
logRequest(method, url, headers, body) {
|
|
154
|
+
if (!this.enabled) return;
|
|
155
|
+
const parts = [
|
|
156
|
+
`\u2192 ${method} ${url}`
|
|
157
|
+
];
|
|
158
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
159
|
+
parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
|
|
160
|
+
}
|
|
161
|
+
const formattedBody = formatBody(sanitizeBody(body));
|
|
162
|
+
if (formattedBody) {
|
|
163
|
+
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
164
|
+
parts.push(` Body: ${truncated}`);
|
|
165
|
+
}
|
|
166
|
+
this.log(parts.join("\n"));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Logs an incoming HTTP response with method, URL, status, duration, and body.
|
|
170
|
+
* Error responses (4xx/5xx) are logged at the error level.
|
|
171
|
+
* @param method - HTTP method (GET, POST, etc.)
|
|
172
|
+
* @param url - The full request URL
|
|
173
|
+
* @param status - HTTP response status code
|
|
174
|
+
* @param durationMs - Request duration in milliseconds
|
|
175
|
+
* @param body - Response body (sensitive fields will be masked, large bodies truncated)
|
|
176
|
+
*/
|
|
177
|
+
logResponse(method, url, status, durationMs, body) {
|
|
178
|
+
if (!this.enabled) return;
|
|
179
|
+
const parts = [
|
|
180
|
+
`\u2190 ${method} ${url} ${status} (${durationMs}ms)`
|
|
181
|
+
];
|
|
182
|
+
const formattedBody = formatBody(sanitizeBody(body));
|
|
183
|
+
if (formattedBody) {
|
|
184
|
+
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
185
|
+
parts.push(` Body: ${truncated}`);
|
|
186
|
+
}
|
|
187
|
+
if (status >= 400) {
|
|
188
|
+
this.error(parts.join("\n"));
|
|
189
|
+
} else {
|
|
190
|
+
this.log(parts.join("\n"));
|
|
133
191
|
}
|
|
134
|
-
return headers;
|
|
135
192
|
}
|
|
136
193
|
};
|
|
137
194
|
|
|
138
195
|
// src/lib/token-manager.ts
|
|
139
|
-
var TOKEN_KEY = "insforge-auth-token";
|
|
140
|
-
var USER_KEY = "insforge-auth-user";
|
|
141
196
|
var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
|
|
142
197
|
function getCsrfToken() {
|
|
143
198
|
if (typeof document === "undefined") return null;
|
|
@@ -157,84 +212,28 @@ function clearCsrfToken() {
|
|
|
157
212
|
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
158
213
|
}
|
|
159
214
|
var TokenManager = class {
|
|
160
|
-
constructor(
|
|
215
|
+
constructor() {
|
|
161
216
|
// In-memory storage
|
|
162
217
|
this.accessToken = null;
|
|
163
218
|
this.user = null;
|
|
164
|
-
//
|
|
165
|
-
this.
|
|
166
|
-
if (storage) {
|
|
167
|
-
this.storage = storage;
|
|
168
|
-
} else if (typeof window !== "undefined" && window.localStorage) {
|
|
169
|
-
this.storage = window.localStorage;
|
|
170
|
-
} else {
|
|
171
|
-
const store = /* @__PURE__ */ new Map();
|
|
172
|
-
this.storage = {
|
|
173
|
-
getItem: (key) => store.get(key) || null,
|
|
174
|
-
setItem: (key, value) => {
|
|
175
|
-
store.set(key, value);
|
|
176
|
-
},
|
|
177
|
-
removeItem: (key) => {
|
|
178
|
-
store.delete(key);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Get current mode
|
|
185
|
-
*/
|
|
186
|
-
get mode() {
|
|
187
|
-
return this._mode;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Set mode to memory (new backend with cookies + memory)
|
|
191
|
-
*/
|
|
192
|
-
setMemoryMode() {
|
|
193
|
-
if (this._mode === "storage") {
|
|
194
|
-
this.storage.removeItem(TOKEN_KEY);
|
|
195
|
-
this.storage.removeItem(USER_KEY);
|
|
196
|
-
}
|
|
197
|
-
this._mode = "memory";
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Set mode to storage (legacy backend with localStorage)
|
|
201
|
-
* Also loads existing session from localStorage
|
|
202
|
-
*/
|
|
203
|
-
setStorageMode() {
|
|
204
|
-
this._mode = "storage";
|
|
205
|
-
this.loadFromStorage();
|
|
219
|
+
// Callback for token changes (used by realtime to reconnect with new token)
|
|
220
|
+
this.onTokenChange = null;
|
|
206
221
|
}
|
|
207
222
|
/**
|
|
208
|
-
*
|
|
209
|
-
*/
|
|
210
|
-
loadFromStorage() {
|
|
211
|
-
const token = this.storage.getItem(TOKEN_KEY);
|
|
212
|
-
const userStr = this.storage.getItem(USER_KEY);
|
|
213
|
-
if (token && userStr) {
|
|
214
|
-
try {
|
|
215
|
-
this.accessToken = token;
|
|
216
|
-
this.user = JSON.parse(userStr);
|
|
217
|
-
} catch {
|
|
218
|
-
this.clearSession();
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Save session (memory always, localStorage only in storage mode)
|
|
223
|
+
* Save session in memory
|
|
224
224
|
*/
|
|
225
225
|
saveSession(session) {
|
|
226
|
+
const tokenChanged = session.accessToken !== this.accessToken;
|
|
226
227
|
this.accessToken = session.accessToken;
|
|
227
228
|
this.user = session.user;
|
|
228
|
-
if (this.
|
|
229
|
-
this.
|
|
230
|
-
this.storage.setItem(USER_KEY, JSON.stringify(session.user));
|
|
229
|
+
if (tokenChanged && this.onTokenChange) {
|
|
230
|
+
this.onTokenChange();
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
/**
|
|
234
234
|
* Get current session
|
|
235
235
|
*/
|
|
236
236
|
getSession() {
|
|
237
|
-
this.loadFromStorage();
|
|
238
237
|
if (!this.accessToken || !this.user) return null;
|
|
239
238
|
return {
|
|
240
239
|
accessToken: this.accessToken,
|
|
@@ -245,16 +244,16 @@ var TokenManager = class {
|
|
|
245
244
|
* Get access token
|
|
246
245
|
*/
|
|
247
246
|
getAccessToken() {
|
|
248
|
-
this.loadFromStorage();
|
|
249
247
|
return this.accessToken;
|
|
250
248
|
}
|
|
251
249
|
/**
|
|
252
250
|
* Set access token
|
|
253
251
|
*/
|
|
254
252
|
setAccessToken(token) {
|
|
253
|
+
const tokenChanged = token !== this.accessToken;
|
|
255
254
|
this.accessToken = token;
|
|
256
|
-
if (this.
|
|
257
|
-
this.
|
|
255
|
+
if (tokenChanged && this.onTokenChange) {
|
|
256
|
+
this.onTokenChange();
|
|
258
257
|
}
|
|
259
258
|
}
|
|
260
259
|
/**
|
|
@@ -268,479 +267,599 @@ var TokenManager = class {
|
|
|
268
267
|
*/
|
|
269
268
|
setUser(user) {
|
|
270
269
|
this.user = user;
|
|
271
|
-
if (this._mode === "storage") {
|
|
272
|
-
this.storage.setItem(USER_KEY, JSON.stringify(user));
|
|
273
|
-
}
|
|
274
270
|
}
|
|
275
271
|
/**
|
|
276
|
-
* Clear
|
|
272
|
+
* Clear in-memory session
|
|
277
273
|
*/
|
|
278
274
|
clearSession() {
|
|
275
|
+
const hadToken = this.accessToken !== null;
|
|
279
276
|
this.accessToken = null;
|
|
280
277
|
this.user = null;
|
|
281
|
-
this.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Check if there's a session in localStorage (for legacy detection)
|
|
286
|
-
*/
|
|
287
|
-
hasStoredSession() {
|
|
288
|
-
const token = this.storage.getItem(TOKEN_KEY);
|
|
289
|
-
return !!token;
|
|
278
|
+
if (hadToken && this.onTokenChange) {
|
|
279
|
+
this.onTokenChange();
|
|
280
|
+
}
|
|
290
281
|
}
|
|
291
282
|
};
|
|
292
283
|
|
|
293
|
-
// src/
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
const { hostname, port, protocol } = window.location;
|
|
299
|
-
if (hostname === "localhost" && port === "7130") {
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
if (protocol === "https:" && hostname.endsWith(".insforge.app")) {
|
|
303
|
-
return true;
|
|
304
|
-
}
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
var Auth = class {
|
|
308
|
-
constructor(http, tokenManager) {
|
|
309
|
-
this.http = http;
|
|
310
|
-
this.tokenManager = tokenManager;
|
|
311
|
-
this.detectAuthCallback();
|
|
312
|
-
}
|
|
284
|
+
// src/lib/http-client.ts
|
|
285
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
286
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
287
|
+
var HttpClient = class {
|
|
313
288
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
289
|
+
* Creates a new HttpClient instance.
|
|
290
|
+
* @param config - SDK configuration including baseUrl, timeout, retry settings, and fetch implementation.
|
|
291
|
+
* @param tokenManager - Token manager for session persistence.
|
|
292
|
+
* @param logger - Optional logger instance for request/response debugging.
|
|
317
293
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// They'll be populated when calling getCurrentUser()
|
|
341
|
-
emailVerified: false,
|
|
342
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
343
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
this.tokenManager.saveSession(session);
|
|
347
|
-
this.http.setAuthToken(accessToken);
|
|
348
|
-
const url = new URL(window.location.href);
|
|
349
|
-
url.searchParams.delete("access_token");
|
|
350
|
-
url.searchParams.delete("user_id");
|
|
351
|
-
url.searchParams.delete("email");
|
|
352
|
-
url.searchParams.delete("name");
|
|
353
|
-
url.searchParams.delete("csrf_token");
|
|
354
|
-
if (params.has("error")) {
|
|
355
|
-
url.searchParams.delete("error");
|
|
356
|
-
}
|
|
357
|
-
window.history.replaceState({}, document.title, url.toString());
|
|
358
|
-
}
|
|
359
|
-
} catch (error) {
|
|
360
|
-
console.debug("OAuth callback detection skipped:", error);
|
|
294
|
+
constructor(config, tokenManager, logger) {
|
|
295
|
+
this.userToken = null;
|
|
296
|
+
this.autoRefreshToken = true;
|
|
297
|
+
this.isRefreshing = false;
|
|
298
|
+
this.refreshPromise = null;
|
|
299
|
+
this.refreshToken = null;
|
|
300
|
+
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
301
|
+
this.autoRefreshToken = config.autoRefreshToken ?? true;
|
|
302
|
+
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
303
|
+
this.anonKey = config.anonKey;
|
|
304
|
+
this.defaultHeaders = {
|
|
305
|
+
...config.headers
|
|
306
|
+
};
|
|
307
|
+
this.tokenManager = tokenManager ?? new TokenManager();
|
|
308
|
+
this.logger = logger || new Logger(false);
|
|
309
|
+
this.timeout = config.timeout ?? 3e4;
|
|
310
|
+
this.retryCount = config.retryCount ?? 3;
|
|
311
|
+
this.retryDelay = config.retryDelay ?? 500;
|
|
312
|
+
if (!this.fetch) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
"Fetch is not available. Please provide a fetch implementation in the config."
|
|
315
|
+
);
|
|
361
316
|
}
|
|
362
317
|
}
|
|
363
318
|
/**
|
|
364
|
-
*
|
|
319
|
+
* Builds a full URL from a path and optional query parameters.
|
|
320
|
+
* Normalizes PostgREST select parameters for proper syntax.
|
|
365
321
|
*/
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (response.csrfToken) {
|
|
377
|
-
setCsrfToken(response.csrfToken);
|
|
322
|
+
buildUrl(path, params) {
|
|
323
|
+
const url = new URL(path, this.baseUrl);
|
|
324
|
+
if (params) {
|
|
325
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
326
|
+
if (key === "select") {
|
|
327
|
+
let normalizedValue = value.replace(/\s+/g, " ").trim();
|
|
328
|
+
normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
|
|
329
|
+
url.searchParams.append(key, normalizedValue);
|
|
330
|
+
} else {
|
|
331
|
+
url.searchParams.append(key, value);
|
|
378
332
|
}
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
data: response,
|
|
382
|
-
error: null
|
|
383
|
-
};
|
|
384
|
-
} catch (error) {
|
|
385
|
-
if (error instanceof InsForgeError) {
|
|
386
|
-
return { data: null, error };
|
|
387
|
-
}
|
|
388
|
-
return {
|
|
389
|
-
data: null,
|
|
390
|
-
error: new InsForgeError(
|
|
391
|
-
error instanceof Error ? error.message : "An unexpected error occurred during sign up",
|
|
392
|
-
500,
|
|
393
|
-
"UNEXPECTED_ERROR"
|
|
394
|
-
)
|
|
395
|
-
};
|
|
333
|
+
});
|
|
396
334
|
}
|
|
335
|
+
return url.toString();
|
|
336
|
+
}
|
|
337
|
+
/** Checks if an HTTP status code is eligible for retry (5xx server errors). */
|
|
338
|
+
isRetryableStatus(status) {
|
|
339
|
+
return RETRYABLE_STATUS_CODES.has(status);
|
|
397
340
|
}
|
|
398
341
|
/**
|
|
399
|
-
*
|
|
342
|
+
* Computes the delay before the next retry using exponential backoff with jitter.
|
|
343
|
+
* @param attempt - The current retry attempt number (1-based).
|
|
344
|
+
* @returns Delay in milliseconds.
|
|
400
345
|
*/
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const session = {
|
|
406
|
-
accessToken: response.accessToken,
|
|
407
|
-
user: response.user
|
|
408
|
-
};
|
|
409
|
-
this.tokenManager.saveSession(session);
|
|
410
|
-
this.http.setAuthToken(response.accessToken);
|
|
411
|
-
if (response.csrfToken) {
|
|
412
|
-
setCsrfToken(response.csrfToken);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return {
|
|
416
|
-
data: response,
|
|
417
|
-
error: null
|
|
418
|
-
};
|
|
419
|
-
} catch (error) {
|
|
420
|
-
if (error instanceof InsForgeError) {
|
|
421
|
-
return { data: null, error };
|
|
422
|
-
}
|
|
423
|
-
return {
|
|
424
|
-
data: null,
|
|
425
|
-
error: new InsForgeError(
|
|
426
|
-
"An unexpected error occurred during sign in",
|
|
427
|
-
500,
|
|
428
|
-
"UNEXPECTED_ERROR"
|
|
429
|
-
)
|
|
430
|
-
};
|
|
431
|
-
}
|
|
346
|
+
computeRetryDelay(attempt) {
|
|
347
|
+
const base = this.retryDelay * Math.pow(2, attempt - 1);
|
|
348
|
+
const jitter = base * (0.85 + Math.random() * 0.3);
|
|
349
|
+
return Math.round(jitter);
|
|
432
350
|
}
|
|
433
351
|
/**
|
|
434
|
-
*
|
|
352
|
+
* Performs an HTTP request with automatic retry and timeout handling.
|
|
353
|
+
* Retries on network errors and 5xx server errors with exponential backoff.
|
|
354
|
+
* Client errors (4xx) and timeouts are thrown immediately without retry.
|
|
355
|
+
* @param method - HTTP method (GET, POST, PUT, PATCH, DELETE).
|
|
356
|
+
* @param path - API path relative to the base URL.
|
|
357
|
+
* @param options - Optional request configuration including headers, body, and query params.
|
|
358
|
+
* @returns Parsed response data.
|
|
359
|
+
* @throws {InsForgeError} On timeout, network failure, or HTTP error responses.
|
|
435
360
|
*/
|
|
436
|
-
async
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
361
|
+
async handleRequest(method, path, options = {}) {
|
|
362
|
+
const {
|
|
363
|
+
params,
|
|
364
|
+
headers = {},
|
|
365
|
+
body,
|
|
366
|
+
signal: callerSignal,
|
|
367
|
+
...fetchOptions
|
|
368
|
+
} = options;
|
|
369
|
+
const url = this.buildUrl(path, params);
|
|
370
|
+
const startTime = Date.now();
|
|
371
|
+
const canRetry = IDEMPOTENT_METHODS.has(method.toUpperCase()) || options.idempotent === true;
|
|
372
|
+
const maxAttempts = canRetry ? this.retryCount : 0;
|
|
373
|
+
const requestHeaders = {
|
|
374
|
+
...this.defaultHeaders
|
|
375
|
+
};
|
|
376
|
+
const authToken = this.userToken || this.anonKey;
|
|
377
|
+
if (authToken) {
|
|
378
|
+
requestHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
379
|
+
}
|
|
380
|
+
let processedBody;
|
|
381
|
+
if (body !== void 0) {
|
|
382
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
383
|
+
processedBody = body;
|
|
384
|
+
} else {
|
|
385
|
+
if (method !== "GET") {
|
|
386
|
+
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
387
|
+
}
|
|
388
|
+
processedBody = JSON.stringify(body);
|
|
456
389
|
}
|
|
457
|
-
return {
|
|
458
|
-
data: {},
|
|
459
|
-
error: new InsForgeError(
|
|
460
|
-
"An unexpected error occurred during OAuth initialization",
|
|
461
|
-
500,
|
|
462
|
-
"UNEXPECTED_ERROR"
|
|
463
|
-
)
|
|
464
|
-
};
|
|
465
390
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
391
|
+
if (headers instanceof Headers) {
|
|
392
|
+
headers.forEach((value, key) => {
|
|
393
|
+
requestHeaders[key] = value;
|
|
394
|
+
});
|
|
395
|
+
} else if (Array.isArray(headers)) {
|
|
396
|
+
headers.forEach(([key, value]) => {
|
|
397
|
+
requestHeaders[key] = value;
|
|
398
|
+
});
|
|
399
|
+
} else {
|
|
400
|
+
Object.assign(requestHeaders, headers);
|
|
401
|
+
}
|
|
402
|
+
this.logger.logRequest(method, url, requestHeaders, processedBody);
|
|
403
|
+
let lastError;
|
|
404
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
405
|
+
if (attempt > 0) {
|
|
406
|
+
const delay = this.computeRetryDelay(attempt);
|
|
407
|
+
this.logger.warn(
|
|
408
|
+
`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
|
|
409
|
+
);
|
|
410
|
+
if (callerSignal?.aborted) throw callerSignal.reason;
|
|
411
|
+
await new Promise((resolve, reject) => {
|
|
412
|
+
const onAbort = () => {
|
|
413
|
+
clearTimeout(timer2);
|
|
414
|
+
reject(callerSignal.reason);
|
|
415
|
+
};
|
|
416
|
+
const timer2 = setTimeout(() => {
|
|
417
|
+
if (callerSignal)
|
|
418
|
+
callerSignal.removeEventListener("abort", onAbort);
|
|
419
|
+
resolve();
|
|
420
|
+
}, delay);
|
|
421
|
+
if (callerSignal) {
|
|
422
|
+
callerSignal.addEventListener("abort", onAbort, { once: true });
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
let controller;
|
|
427
|
+
let timer;
|
|
428
|
+
if (this.timeout > 0 || callerSignal) {
|
|
429
|
+
controller = new AbortController();
|
|
430
|
+
if (this.timeout > 0) {
|
|
431
|
+
timer = setTimeout(() => controller.abort(), this.timeout);
|
|
432
|
+
}
|
|
433
|
+
if (callerSignal) {
|
|
434
|
+
if (callerSignal.aborted) {
|
|
435
|
+
controller.abort(callerSignal.reason);
|
|
436
|
+
} else {
|
|
437
|
+
const onCallerAbort = () => controller.abort(callerSignal.reason);
|
|
438
|
+
callerSignal.addEventListener("abort", onCallerAbort, {
|
|
439
|
+
once: true
|
|
440
|
+
});
|
|
441
|
+
controller.signal.addEventListener(
|
|
442
|
+
"abort",
|
|
443
|
+
() => {
|
|
444
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
445
|
+
},
|
|
446
|
+
{ once: true }
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
472
451
|
try {
|
|
473
|
-
await this.
|
|
474
|
-
|
|
452
|
+
const response = await this.fetch(url, {
|
|
453
|
+
method,
|
|
454
|
+
headers: requestHeaders,
|
|
455
|
+
body: processedBody,
|
|
456
|
+
...fetchOptions,
|
|
457
|
+
...controller ? { signal: controller.signal } : {}
|
|
458
|
+
});
|
|
459
|
+
if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
|
|
460
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
461
|
+
await response.body?.cancel();
|
|
462
|
+
lastError = new InsForgeError(
|
|
463
|
+
`Server error: ${response.status} ${response.statusText}`,
|
|
464
|
+
response.status,
|
|
465
|
+
"SERVER_ERROR"
|
|
466
|
+
);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (response.status === 204) {
|
|
470
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
471
|
+
return void 0;
|
|
472
|
+
}
|
|
473
|
+
let data;
|
|
474
|
+
const contentType = response.headers.get("content-type");
|
|
475
|
+
try {
|
|
476
|
+
if (contentType?.includes("json")) {
|
|
477
|
+
data = await response.json();
|
|
478
|
+
} else {
|
|
479
|
+
data = await response.text();
|
|
480
|
+
}
|
|
481
|
+
} catch (parseErr) {
|
|
482
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
483
|
+
throw new InsForgeError(
|
|
484
|
+
`Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
|
|
485
|
+
response.status,
|
|
486
|
+
response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
this.logger.logResponse(
|
|
492
|
+
method,
|
|
493
|
+
url,
|
|
494
|
+
response.status,
|
|
495
|
+
Date.now() - startTime,
|
|
496
|
+
data
|
|
497
|
+
);
|
|
498
|
+
if (data && typeof data === "object" && "error" in data) {
|
|
499
|
+
if (!data.statusCode && !data.status) {
|
|
500
|
+
data.statusCode = response.status;
|
|
501
|
+
}
|
|
502
|
+
const error = InsForgeError.fromApiError(data);
|
|
503
|
+
Object.keys(data).forEach((key) => {
|
|
504
|
+
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
505
|
+
error[key] = data[key];
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
throw new InsForgeError(
|
|
511
|
+
`Request failed: ${response.statusText}`,
|
|
512
|
+
response.status,
|
|
513
|
+
"REQUEST_FAILED"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
this.logger.logResponse(
|
|
517
|
+
method,
|
|
518
|
+
url,
|
|
519
|
+
response.status,
|
|
520
|
+
Date.now() - startTime,
|
|
521
|
+
data
|
|
522
|
+
);
|
|
523
|
+
return data;
|
|
524
|
+
} catch (err) {
|
|
525
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
526
|
+
if (err?.name === "AbortError") {
|
|
527
|
+
if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
528
|
+
throw new InsForgeError(
|
|
529
|
+
`Request timed out after ${this.timeout}ms`,
|
|
530
|
+
408,
|
|
531
|
+
"REQUEST_TIMEOUT"
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
throw err;
|
|
535
|
+
}
|
|
536
|
+
if (err instanceof InsForgeError) {
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
if (attempt < maxAttempts) {
|
|
540
|
+
lastError = err;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
throw new InsForgeError(
|
|
544
|
+
`Network request failed: ${err?.message || "Unknown error"}`,
|
|
545
|
+
0,
|
|
546
|
+
"NETWORK_ERROR"
|
|
547
|
+
);
|
|
475
548
|
}
|
|
476
|
-
this.tokenManager.clearSession();
|
|
477
|
-
this.http.setAuthToken(null);
|
|
478
|
-
clearCsrfToken();
|
|
479
|
-
return { error: null };
|
|
480
|
-
} catch (error) {
|
|
481
|
-
return {
|
|
482
|
-
error: new InsForgeError(
|
|
483
|
-
"Failed to sign out",
|
|
484
|
-
500,
|
|
485
|
-
"SIGNOUT_ERROR"
|
|
486
|
-
)
|
|
487
|
-
};
|
|
488
549
|
}
|
|
550
|
+
throw lastError || new InsForgeError(
|
|
551
|
+
"Request failed after all retry attempts",
|
|
552
|
+
0,
|
|
553
|
+
"NETWORK_ERROR"
|
|
554
|
+
);
|
|
489
555
|
}
|
|
490
|
-
|
|
491
|
-
* Get all public authentication configuration (OAuth + Email)
|
|
492
|
-
* Returns both OAuth providers and email authentication settings in one request
|
|
493
|
-
* This is a public endpoint that doesn't require authentication
|
|
494
|
-
*
|
|
495
|
-
* @returns Complete public authentication configuration including OAuth providers and email auth settings
|
|
496
|
-
*
|
|
497
|
-
* @example
|
|
498
|
-
* ```ts
|
|
499
|
-
* const { data, error } = await insforge.auth.getPublicAuthConfig();
|
|
500
|
-
* if (data) {
|
|
501
|
-
* console.log(`OAuth providers: ${data.oauth.data.length}`);
|
|
502
|
-
* console.log(`Password min length: ${data.email.passwordMinLength}`);
|
|
503
|
-
* }
|
|
504
|
-
* ```
|
|
505
|
-
*/
|
|
506
|
-
async getPublicAuthConfig() {
|
|
556
|
+
async request(method, path, options = {}) {
|
|
507
557
|
try {
|
|
508
|
-
|
|
509
|
-
return {
|
|
510
|
-
data: response,
|
|
511
|
-
error: null
|
|
512
|
-
};
|
|
558
|
+
return await this.handleRequest(method, path, { ...options });
|
|
513
559
|
} catch (error) {
|
|
514
|
-
if (error instanceof InsForgeError) {
|
|
515
|
-
|
|
560
|
+
if (error instanceof InsForgeError && error.statusCode === 401 && error.error === "INVALID_TOKEN" && this.autoRefreshToken) {
|
|
561
|
+
try {
|
|
562
|
+
const newTokenData = await this.handleTokenRefresh();
|
|
563
|
+
this.setAuthToken(newTokenData.accessToken);
|
|
564
|
+
this.tokenManager.saveSession(newTokenData);
|
|
565
|
+
if (newTokenData.csrfToken) {
|
|
566
|
+
setCsrfToken(newTokenData.csrfToken);
|
|
567
|
+
}
|
|
568
|
+
if (newTokenData.refreshToken) {
|
|
569
|
+
this.setRefreshToken(newTokenData.refreshToken);
|
|
570
|
+
}
|
|
571
|
+
return await this.handleRequest(method, path, { ...options });
|
|
572
|
+
} catch (error2) {
|
|
573
|
+
this.tokenManager.clearSession();
|
|
574
|
+
this.userToken = null;
|
|
575
|
+
this.refreshToken = null;
|
|
576
|
+
clearCsrfToken();
|
|
577
|
+
throw error2;
|
|
578
|
+
}
|
|
516
579
|
}
|
|
517
|
-
|
|
518
|
-
data: null,
|
|
519
|
-
error: new InsForgeError(
|
|
520
|
-
"An unexpected error occurred while fetching public authentication configuration",
|
|
521
|
-
500,
|
|
522
|
-
"UNEXPECTED_ERROR"
|
|
523
|
-
)
|
|
524
|
-
};
|
|
580
|
+
throw error;
|
|
525
581
|
}
|
|
526
582
|
}
|
|
527
|
-
/**
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
583
|
+
/** Performs a GET request. */
|
|
584
|
+
get(path, options) {
|
|
585
|
+
return this.request("GET", path, options);
|
|
586
|
+
}
|
|
587
|
+
/** Performs a POST request with an optional JSON body. */
|
|
588
|
+
post(path, body, options) {
|
|
589
|
+
return this.request("POST", path, { ...options, body });
|
|
590
|
+
}
|
|
591
|
+
/** Performs a PUT request with an optional JSON body. */
|
|
592
|
+
put(path, body, options) {
|
|
593
|
+
return this.request("PUT", path, { ...options, body });
|
|
594
|
+
}
|
|
595
|
+
/** Performs a PATCH request with an optional JSON body. */
|
|
596
|
+
patch(path, body, options) {
|
|
597
|
+
return this.request("PATCH", path, { ...options, body });
|
|
598
|
+
}
|
|
599
|
+
/** Performs a DELETE request. */
|
|
600
|
+
delete(path, options) {
|
|
601
|
+
return this.request("DELETE", path, options);
|
|
602
|
+
}
|
|
603
|
+
/** Sets or clears the user authentication token for subsequent requests. */
|
|
604
|
+
setAuthToken(token) {
|
|
605
|
+
this.userToken = token;
|
|
606
|
+
}
|
|
607
|
+
setRefreshToken(token) {
|
|
608
|
+
this.refreshToken = token;
|
|
609
|
+
}
|
|
610
|
+
/** Returns the current default headers including the authorization header if set. */
|
|
611
|
+
getHeaders() {
|
|
612
|
+
const headers = { ...this.defaultHeaders };
|
|
613
|
+
const authToken = this.userToken || this.anonKey;
|
|
614
|
+
if (authToken) {
|
|
615
|
+
headers["Authorization"] = `Bearer ${authToken}`;
|
|
616
|
+
}
|
|
617
|
+
return headers;
|
|
618
|
+
}
|
|
619
|
+
async handleTokenRefresh() {
|
|
620
|
+
if (this.isRefreshing) {
|
|
621
|
+
return this.refreshPromise;
|
|
565
622
|
}
|
|
623
|
+
this.isRefreshing = true;
|
|
624
|
+
this.refreshPromise = (async () => {
|
|
625
|
+
try {
|
|
626
|
+
const csrfToken = getCsrfToken();
|
|
627
|
+
const body = this.refreshToken ? { refreshToken: this.refreshToken } : void 0;
|
|
628
|
+
const response = await this.handleRequest(
|
|
629
|
+
"POST",
|
|
630
|
+
"/api/auth/sessions/current",
|
|
631
|
+
{
|
|
632
|
+
body,
|
|
633
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
634
|
+
credentials: "include"
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
return response;
|
|
638
|
+
} finally {
|
|
639
|
+
this.isRefreshing = false;
|
|
640
|
+
this.refreshPromise = null;
|
|
641
|
+
}
|
|
642
|
+
})();
|
|
643
|
+
return this.refreshPromise;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/modules/auth/helpers.ts
|
|
648
|
+
var PKCE_VERIFIER_KEY = "insforge_pkce_verifier";
|
|
649
|
+
function base64UrlEncode(buffer) {
|
|
650
|
+
const base64 = btoa(String.fromCharCode(...buffer));
|
|
651
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
652
|
+
}
|
|
653
|
+
function generateCodeVerifier() {
|
|
654
|
+
const array = new Uint8Array(32);
|
|
655
|
+
crypto.getRandomValues(array);
|
|
656
|
+
return base64UrlEncode(array);
|
|
657
|
+
}
|
|
658
|
+
async function generateCodeChallenge(verifier) {
|
|
659
|
+
const encoder = new TextEncoder();
|
|
660
|
+
const data = encoder.encode(verifier);
|
|
661
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
662
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
663
|
+
}
|
|
664
|
+
function storePkceVerifier(verifier) {
|
|
665
|
+
if (typeof sessionStorage !== "undefined") {
|
|
666
|
+
sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function retrievePkceVerifier() {
|
|
670
|
+
if (typeof sessionStorage === "undefined") {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
|
|
674
|
+
if (verifier) {
|
|
675
|
+
sessionStorage.removeItem(PKCE_VERIFIER_KEY);
|
|
676
|
+
}
|
|
677
|
+
return verifier;
|
|
678
|
+
}
|
|
679
|
+
function wrapError(error, fallbackMessage) {
|
|
680
|
+
if (error instanceof InsForgeError) {
|
|
681
|
+
return { data: null, error };
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
data: null,
|
|
685
|
+
error: new InsForgeError(
|
|
686
|
+
error instanceof Error ? error.message : fallbackMessage,
|
|
687
|
+
500,
|
|
688
|
+
"UNEXPECTED_ERROR"
|
|
689
|
+
)
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function cleanUrlParams(...params) {
|
|
693
|
+
if (typeof window === "undefined") {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const url = new URL(window.location.href);
|
|
697
|
+
params.forEach((p) => url.searchParams.delete(p));
|
|
698
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/modules/auth/auth.ts
|
|
702
|
+
import { oAuthProvidersSchema } from "@insforge/shared-schemas";
|
|
703
|
+
var Auth = class {
|
|
704
|
+
constructor(http, tokenManager, options = {}) {
|
|
705
|
+
this.http = http;
|
|
706
|
+
this.tokenManager = tokenManager;
|
|
707
|
+
this.options = options;
|
|
708
|
+
this.authCallbackHandled = this.detectAuthCallback();
|
|
709
|
+
}
|
|
710
|
+
isServerMode() {
|
|
711
|
+
return !!this.options.isServerMode;
|
|
566
712
|
}
|
|
567
713
|
/**
|
|
568
|
-
*
|
|
569
|
-
*
|
|
714
|
+
* Save session from API response
|
|
715
|
+
* Handles token storage, CSRF token, and HTTP auth header
|
|
570
716
|
*/
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
error: new InsForgeError(
|
|
585
|
-
"An unexpected error occurred while fetching user profile",
|
|
586
|
-
500,
|
|
587
|
-
"UNEXPECTED_ERROR"
|
|
588
|
-
)
|
|
589
|
-
};
|
|
717
|
+
saveSessionFromResponse(response) {
|
|
718
|
+
if (!response.accessToken || !response.user) {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
const session = {
|
|
722
|
+
accessToken: response.accessToken,
|
|
723
|
+
user: response.user
|
|
724
|
+
};
|
|
725
|
+
if (!this.isServerMode() && response.csrfToken) {
|
|
726
|
+
setCsrfToken(response.csrfToken);
|
|
727
|
+
}
|
|
728
|
+
if (!this.isServerMode()) {
|
|
729
|
+
this.tokenManager.saveSession(session);
|
|
590
730
|
}
|
|
731
|
+
this.http.setAuthToken(response.accessToken);
|
|
732
|
+
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
733
|
+
return true;
|
|
591
734
|
}
|
|
735
|
+
// ============================================================================
|
|
736
|
+
// OAuth Callback Detection (runs on initialization)
|
|
737
|
+
// ============================================================================
|
|
592
738
|
/**
|
|
593
|
-
*
|
|
594
|
-
*
|
|
739
|
+
* Detect and handle OAuth callback parameters in URL
|
|
740
|
+
* Supports PKCE flow (insforge_code)
|
|
595
741
|
*/
|
|
596
|
-
async
|
|
742
|
+
async detectAuthCallback() {
|
|
743
|
+
if (this.isServerMode() || typeof window === "undefined") return;
|
|
597
744
|
try {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
745
|
+
const params = new URLSearchParams(window.location.search);
|
|
746
|
+
const error = params.get("error");
|
|
747
|
+
if (error) {
|
|
748
|
+
cleanUrlParams("error");
|
|
749
|
+
console.debug("OAuth callback error:", error);
|
|
750
|
+
return;
|
|
602
751
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
{
|
|
610
|
-
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
611
|
-
credentials: "include"
|
|
612
|
-
}
|
|
613
|
-
);
|
|
614
|
-
if (response.accessToken) {
|
|
615
|
-
this.tokenManager.setMemoryMode();
|
|
616
|
-
this.tokenManager.setAccessToken(response.accessToken);
|
|
617
|
-
this.http.setAuthToken(response.accessToken);
|
|
618
|
-
if (response.user) {
|
|
619
|
-
this.tokenManager.setUser(response.user);
|
|
620
|
-
}
|
|
621
|
-
if (response.csrfToken) {
|
|
622
|
-
setCsrfToken(response.csrfToken);
|
|
623
|
-
}
|
|
624
|
-
return {
|
|
625
|
-
data: { session: this.tokenManager.getSession() },
|
|
626
|
-
error: null
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
} catch (error) {
|
|
630
|
-
if (error instanceof InsForgeError) {
|
|
631
|
-
if (error.statusCode === 404) {
|
|
632
|
-
this.tokenManager.setStorageMode();
|
|
633
|
-
const session2 = this.tokenManager.getSession();
|
|
634
|
-
if (session2) {
|
|
635
|
-
return { data: { session: session2 }, error: null };
|
|
636
|
-
}
|
|
637
|
-
return { data: { session: null }, error: null };
|
|
638
|
-
}
|
|
639
|
-
return { data: { session: null }, error };
|
|
640
|
-
}
|
|
752
|
+
const code = params.get("insforge_code");
|
|
753
|
+
if (code) {
|
|
754
|
+
cleanUrlParams("insforge_code");
|
|
755
|
+
const { error: exchangeError } = await this.exchangeOAuthCode(code);
|
|
756
|
+
if (exchangeError) {
|
|
757
|
+
console.debug("OAuth code exchange failed:", exchangeError.message);
|
|
641
758
|
}
|
|
759
|
+
return;
|
|
642
760
|
}
|
|
643
|
-
return { data: { session: null }, error: null };
|
|
644
761
|
} catch (error) {
|
|
645
|
-
|
|
646
|
-
return { data: { session: null }, error };
|
|
647
|
-
}
|
|
648
|
-
return {
|
|
649
|
-
data: { session: null },
|
|
650
|
-
error: new InsForgeError(
|
|
651
|
-
"An unexpected error occurred while getting session",
|
|
652
|
-
500,
|
|
653
|
-
"UNEXPECTED_ERROR"
|
|
654
|
-
)
|
|
655
|
-
};
|
|
762
|
+
console.debug("OAuth callback detection skipped:", error);
|
|
656
763
|
}
|
|
657
764
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
*/
|
|
663
|
-
async setProfile(profile) {
|
|
765
|
+
// ============================================================================
|
|
766
|
+
// Sign Up / Sign In / Sign Out
|
|
767
|
+
// ============================================================================
|
|
768
|
+
async signUp(request) {
|
|
664
769
|
try {
|
|
665
|
-
const response = await this.http.
|
|
666
|
-
"/api/auth/
|
|
667
|
-
|
|
770
|
+
const response = await this.http.post(
|
|
771
|
+
this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
|
|
772
|
+
request,
|
|
773
|
+
{ credentials: "include" }
|
|
668
774
|
);
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
error: null
|
|
672
|
-
};
|
|
673
|
-
} catch (error) {
|
|
674
|
-
if (error instanceof InsForgeError) {
|
|
675
|
-
return { data: null, error };
|
|
775
|
+
if (response.accessToken && response.user) {
|
|
776
|
+
this.saveSessionFromResponse(response);
|
|
676
777
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
)
|
|
684
|
-
};
|
|
778
|
+
if (response.refreshToken) {
|
|
779
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
780
|
+
}
|
|
781
|
+
return { data: response, error: null };
|
|
782
|
+
} catch (error) {
|
|
783
|
+
return wrapError(error, "An unexpected error occurred during sign up");
|
|
685
784
|
}
|
|
686
785
|
}
|
|
687
|
-
|
|
688
|
-
* Send email verification (code or link based on config)
|
|
689
|
-
*
|
|
690
|
-
* Send email verification using the method configured in auth settings (verifyEmailMethod).
|
|
691
|
-
* When method is 'code', sends a 6-digit numeric code. When method is 'link', sends a magic link.
|
|
692
|
-
* Prevents user enumeration by returning success even if email doesn't exist.
|
|
693
|
-
*/
|
|
694
|
-
async sendVerificationEmail(request) {
|
|
786
|
+
async signInWithPassword(request) {
|
|
695
787
|
try {
|
|
696
788
|
const response = await this.http.post(
|
|
697
|
-
"/api/auth/
|
|
698
|
-
request
|
|
789
|
+
this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
|
|
790
|
+
request,
|
|
791
|
+
{ credentials: "include" }
|
|
699
792
|
);
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
793
|
+
this.saveSessionFromResponse(response);
|
|
794
|
+
if (response.refreshToken) {
|
|
795
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
796
|
+
}
|
|
797
|
+
return { data: response, error: null };
|
|
704
798
|
} catch (error) {
|
|
705
|
-
|
|
706
|
-
|
|
799
|
+
return wrapError(error, "An unexpected error occurred during sign in");
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async signOut() {
|
|
803
|
+
try {
|
|
804
|
+
try {
|
|
805
|
+
await this.http.post(
|
|
806
|
+
this.isServerMode() ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
807
|
+
void 0,
|
|
808
|
+
{ credentials: "include" }
|
|
809
|
+
);
|
|
810
|
+
} catch {
|
|
707
811
|
}
|
|
812
|
+
this.tokenManager.clearSession();
|
|
813
|
+
this.http.setAuthToken(null);
|
|
814
|
+
this.http.setRefreshToken(null);
|
|
815
|
+
if (!this.isServerMode()) {
|
|
816
|
+
clearCsrfToken();
|
|
817
|
+
}
|
|
818
|
+
return { error: null };
|
|
819
|
+
} catch {
|
|
708
820
|
return {
|
|
709
|
-
|
|
710
|
-
error: new InsForgeError(
|
|
711
|
-
"An unexpected error occurred while sending verification code",
|
|
712
|
-
500,
|
|
713
|
-
"UNEXPECTED_ERROR"
|
|
714
|
-
)
|
|
821
|
+
error: new InsForgeError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
715
822
|
};
|
|
716
823
|
}
|
|
717
824
|
}
|
|
825
|
+
// ============================================================================
|
|
826
|
+
// OAuth Authentication
|
|
827
|
+
// ============================================================================
|
|
718
828
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* Send password reset email using the method configured in auth settings (resetPasswordMethod).
|
|
722
|
-
* When method is 'code', sends a 6-digit numeric code for two-step flow.
|
|
723
|
-
* When method is 'link', sends a magic link.
|
|
724
|
-
* Prevents user enumeration by returning success even if email doesn't exist.
|
|
829
|
+
* Sign in with OAuth provider using PKCE flow
|
|
725
830
|
*/
|
|
726
|
-
async
|
|
831
|
+
async signInWithOAuth(options) {
|
|
727
832
|
try {
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
833
|
+
const { provider, redirectTo, skipBrowserRedirect } = options;
|
|
834
|
+
const providerKey = encodeURIComponent(provider.toLowerCase());
|
|
835
|
+
const codeVerifier = generateCodeVerifier();
|
|
836
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
837
|
+
storePkceVerifier(codeVerifier);
|
|
838
|
+
const params = { code_challenge: codeChallenge };
|
|
839
|
+
if (redirectTo) params.redirect_uri = redirectTo;
|
|
840
|
+
const isBuiltInProvider = oAuthProvidersSchema.options.includes(
|
|
841
|
+
providerKey
|
|
731
842
|
);
|
|
843
|
+
const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
|
|
844
|
+
const response = await this.http.get(oauthPath, {
|
|
845
|
+
params
|
|
846
|
+
});
|
|
847
|
+
if (!this.isServerMode() && typeof window !== "undefined" && !skipBrowserRedirect) {
|
|
848
|
+
window.location.href = response.authUrl;
|
|
849
|
+
return { data: {}, error: null };
|
|
850
|
+
}
|
|
732
851
|
return {
|
|
733
|
-
data: response,
|
|
852
|
+
data: { url: response.authUrl, provider: providerKey, codeVerifier },
|
|
734
853
|
error: null
|
|
735
854
|
};
|
|
736
855
|
} catch (error) {
|
|
737
856
|
if (error instanceof InsForgeError) {
|
|
738
|
-
return { data:
|
|
857
|
+
return { data: {}, error };
|
|
739
858
|
}
|
|
740
859
|
return {
|
|
741
|
-
data:
|
|
860
|
+
data: {},
|
|
742
861
|
error: new InsForgeError(
|
|
743
|
-
"An unexpected error occurred
|
|
862
|
+
"An unexpected error occurred during OAuth initialization",
|
|
744
863
|
500,
|
|
745
864
|
"UNEXPECTED_ERROR"
|
|
746
865
|
)
|
|
@@ -748,123 +867,292 @@ var Auth = class {
|
|
|
748
867
|
}
|
|
749
868
|
}
|
|
750
869
|
/**
|
|
751
|
-
* Exchange
|
|
752
|
-
*
|
|
753
|
-
* Step 1 of two-step password reset flow (only used when resetPasswordMethod is 'code'):
|
|
754
|
-
* 1. Verify the 6-digit code sent to user's email
|
|
755
|
-
* 2. Return a reset token that can be used to actually reset the password
|
|
756
|
-
*
|
|
757
|
-
* This endpoint is not used when resetPasswordMethod is 'link' (magic link flow is direct).
|
|
870
|
+
* Exchange OAuth authorization code for tokens (PKCE flow)
|
|
871
|
+
* Called automatically on initialization when insforge_code is in URL
|
|
758
872
|
*/
|
|
759
|
-
async
|
|
873
|
+
async exchangeOAuthCode(code, codeVerifier) {
|
|
760
874
|
try {
|
|
875
|
+
const verifier = codeVerifier ?? retrievePkceVerifier();
|
|
876
|
+
if (!verifier) {
|
|
877
|
+
return {
|
|
878
|
+
data: null,
|
|
879
|
+
error: new InsForgeError(
|
|
880
|
+
"PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
|
|
881
|
+
400,
|
|
882
|
+
"PKCE_VERIFIER_MISSING"
|
|
883
|
+
)
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const request = {
|
|
887
|
+
code,
|
|
888
|
+
code_verifier: verifier
|
|
889
|
+
};
|
|
761
890
|
const response = await this.http.post(
|
|
762
|
-
"/api/auth/
|
|
763
|
-
request
|
|
891
|
+
this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
|
|
892
|
+
request,
|
|
893
|
+
{ credentials: "include" }
|
|
764
894
|
);
|
|
895
|
+
this.saveSessionFromResponse(response);
|
|
765
896
|
return {
|
|
766
897
|
data: response,
|
|
767
898
|
error: null
|
|
768
899
|
};
|
|
769
900
|
} catch (error) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
data: null,
|
|
775
|
-
error: new InsForgeError(
|
|
776
|
-
"An unexpected error occurred while verifying reset code",
|
|
777
|
-
500,
|
|
778
|
-
"UNEXPECTED_ERROR"
|
|
779
|
-
)
|
|
780
|
-
};
|
|
901
|
+
return wrapError(
|
|
902
|
+
error,
|
|
903
|
+
"An unexpected error occurred during OAuth code exchange"
|
|
904
|
+
);
|
|
781
905
|
}
|
|
782
906
|
}
|
|
783
907
|
/**
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
* Reset user password with a token. The token can be:
|
|
787
|
-
* - Magic link token (64-character hex token from send-reset-password when method is 'link')
|
|
788
|
-
* - Reset token (from exchange-reset-password-token after code verification when method is 'code')
|
|
789
|
-
*
|
|
790
|
-
* Both token types use RESET_PASSWORD purpose and are verified the same way.
|
|
908
|
+
* Sign in with an ID token from a native SDK (Google One Tap, etc.)
|
|
909
|
+
* Use this for native mobile apps or Google One Tap on web.
|
|
791
910
|
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
* - Link method: send-reset-password → reset-password (with link token directly)
|
|
911
|
+
* @param credentials.provider - The identity provider (currently only 'google' is supported)
|
|
912
|
+
* @param credentials.token - The ID token from the native SDK
|
|
795
913
|
*/
|
|
796
|
-
async
|
|
914
|
+
async signInWithIdToken(credentials) {
|
|
797
915
|
try {
|
|
916
|
+
const { provider, token } = credentials;
|
|
798
917
|
const response = await this.http.post(
|
|
799
|
-
"/api/auth/
|
|
800
|
-
|
|
918
|
+
"/api/auth/id-token?client_type=mobile",
|
|
919
|
+
{ provider, token },
|
|
920
|
+
{ credentials: "include" }
|
|
801
921
|
);
|
|
922
|
+
this.saveSessionFromResponse(response);
|
|
923
|
+
if (response.refreshToken) {
|
|
924
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
925
|
+
}
|
|
802
926
|
return {
|
|
803
927
|
data: response,
|
|
804
928
|
error: null
|
|
805
929
|
};
|
|
806
930
|
} catch (error) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
data: null,
|
|
812
|
-
error: new InsForgeError(
|
|
813
|
-
"An unexpected error occurred while resetting password",
|
|
814
|
-
500,
|
|
815
|
-
"UNEXPECTED_ERROR"
|
|
816
|
-
)
|
|
817
|
-
};
|
|
931
|
+
return wrapError(
|
|
932
|
+
error,
|
|
933
|
+
"An unexpected error occurred during ID token sign in"
|
|
934
|
+
);
|
|
818
935
|
}
|
|
819
936
|
}
|
|
937
|
+
// ============================================================================
|
|
938
|
+
// Session Management
|
|
939
|
+
// ============================================================================
|
|
820
940
|
/**
|
|
821
|
-
*
|
|
941
|
+
* Refresh the current auth session.
|
|
822
942
|
*
|
|
823
|
-
*
|
|
824
|
-
* -
|
|
825
|
-
* - Link verification: Provide only `otp` (64-character hex token from magic link)
|
|
943
|
+
* Browser mode:
|
|
944
|
+
* - Uses httpOnly refresh cookie and optional CSRF header.
|
|
826
945
|
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* The email verification link sent to users always points to the backend API endpoint.
|
|
830
|
-
* If `verifyEmailRedirectTo` is configured, the backend will redirect to that URL after successful verification.
|
|
831
|
-
* Otherwise, a default success page is displayed.
|
|
946
|
+
* Server mode (`isServerMode: true`):
|
|
947
|
+
* - Uses mobile auth flow and requires `refreshToken` in request body.
|
|
832
948
|
*/
|
|
833
|
-
async
|
|
949
|
+
async refreshSession(options) {
|
|
834
950
|
try {
|
|
951
|
+
if (this.isServerMode() && !options?.refreshToken) {
|
|
952
|
+
return {
|
|
953
|
+
data: null,
|
|
954
|
+
error: new InsForgeError(
|
|
955
|
+
"refreshToken is required when refreshing session in server mode",
|
|
956
|
+
400,
|
|
957
|
+
"REFRESH_TOKEN_REQUIRED"
|
|
958
|
+
)
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
|
|
835
962
|
const response = await this.http.post(
|
|
836
|
-
"/api/auth/
|
|
837
|
-
|
|
963
|
+
this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
|
|
964
|
+
this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
|
|
965
|
+
{
|
|
966
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
967
|
+
credentials: "include"
|
|
968
|
+
}
|
|
838
969
|
);
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
970
|
+
if (response.accessToken) {
|
|
971
|
+
this.saveSessionFromResponse(response);
|
|
972
|
+
}
|
|
973
|
+
return { data: response, error: null };
|
|
974
|
+
} catch (error) {
|
|
975
|
+
return wrapError(
|
|
976
|
+
error,
|
|
977
|
+
"An unexpected error occurred during session refresh"
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get current user, automatically waits for pending OAuth callback
|
|
983
|
+
*/
|
|
984
|
+
async getCurrentUser() {
|
|
985
|
+
await this.authCallbackHandled;
|
|
986
|
+
try {
|
|
987
|
+
if (this.isServerMode()) {
|
|
988
|
+
const accessToken = this.tokenManager.getAccessToken();
|
|
989
|
+
if (!accessToken) return { data: { user: null }, error: null };
|
|
990
|
+
this.http.setAuthToken(accessToken);
|
|
991
|
+
const response = await this.http.get(
|
|
992
|
+
"/api/auth/sessions/current"
|
|
993
|
+
);
|
|
994
|
+
const user = response.user ?? null;
|
|
995
|
+
return { data: { user }, error: null };
|
|
996
|
+
}
|
|
997
|
+
const session = this.tokenManager.getSession();
|
|
998
|
+
if (session) {
|
|
999
|
+
this.http.setAuthToken(session.accessToken);
|
|
1000
|
+
return { data: { user: session.user }, error: null };
|
|
1001
|
+
}
|
|
1002
|
+
if (typeof window !== "undefined") {
|
|
1003
|
+
const { data: refreshed, error: refreshError } = await this.refreshSession();
|
|
1004
|
+
if (refreshError) {
|
|
1005
|
+
return { data: { user: null }, error: refreshError };
|
|
1006
|
+
}
|
|
1007
|
+
if (refreshed?.accessToken) {
|
|
1008
|
+
return { data: { user: refreshed.user ?? null }, error: null };
|
|
848
1009
|
}
|
|
849
1010
|
}
|
|
850
|
-
return {
|
|
851
|
-
data: response,
|
|
852
|
-
error: null
|
|
853
|
-
};
|
|
1011
|
+
return { data: { user: null }, error: null };
|
|
854
1012
|
} catch (error) {
|
|
855
1013
|
if (error instanceof InsForgeError) {
|
|
856
|
-
return { data: null, error };
|
|
1014
|
+
return { data: { user: null }, error };
|
|
857
1015
|
}
|
|
858
1016
|
return {
|
|
859
|
-
data: null,
|
|
1017
|
+
data: { user: null },
|
|
860
1018
|
error: new InsForgeError(
|
|
861
|
-
"An unexpected error occurred while
|
|
1019
|
+
"An unexpected error occurred while getting user",
|
|
862
1020
|
500,
|
|
863
1021
|
"UNEXPECTED_ERROR"
|
|
864
1022
|
)
|
|
865
1023
|
};
|
|
866
1024
|
}
|
|
867
1025
|
}
|
|
1026
|
+
// ============================================================================
|
|
1027
|
+
// Profile Management
|
|
1028
|
+
// ============================================================================
|
|
1029
|
+
async getProfile(userId) {
|
|
1030
|
+
try {
|
|
1031
|
+
const response = await this.http.get(
|
|
1032
|
+
`/api/auth/profiles/${userId}`
|
|
1033
|
+
);
|
|
1034
|
+
return { data: response, error: null };
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
return wrapError(
|
|
1037
|
+
error,
|
|
1038
|
+
"An unexpected error occurred while fetching user profile"
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
async setProfile(profile) {
|
|
1043
|
+
try {
|
|
1044
|
+
const response = await this.http.patch(
|
|
1045
|
+
"/api/auth/profiles/current",
|
|
1046
|
+
{
|
|
1047
|
+
profile
|
|
1048
|
+
}
|
|
1049
|
+
);
|
|
1050
|
+
const currentUser = this.tokenManager.getUser();
|
|
1051
|
+
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
1052
|
+
this.tokenManager.setUser({
|
|
1053
|
+
...currentUser,
|
|
1054
|
+
profile: response.profile
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
return { data: response, error: null };
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
return wrapError(
|
|
1060
|
+
error,
|
|
1061
|
+
"An unexpected error occurred while updating user profile"
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
// ============================================================================
|
|
1066
|
+
// Email Verification
|
|
1067
|
+
// ============================================================================
|
|
1068
|
+
async resendVerificationEmail(request) {
|
|
1069
|
+
try {
|
|
1070
|
+
const response = await this.http.post("/api/auth/email/send-verification", request);
|
|
1071
|
+
return { data: response, error: null };
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
return wrapError(
|
|
1074
|
+
error,
|
|
1075
|
+
"An unexpected error occurred while sending verification email"
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async verifyEmail(request) {
|
|
1080
|
+
try {
|
|
1081
|
+
const response = await this.http.post(
|
|
1082
|
+
this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
|
|
1083
|
+
request,
|
|
1084
|
+
{ credentials: "include" }
|
|
1085
|
+
);
|
|
1086
|
+
this.saveSessionFromResponse(response);
|
|
1087
|
+
if (response.refreshToken) {
|
|
1088
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1089
|
+
}
|
|
1090
|
+
return { data: response, error: null };
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
return wrapError(
|
|
1093
|
+
error,
|
|
1094
|
+
"An unexpected error occurred while verifying email"
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
// ============================================================================
|
|
1099
|
+
// Password Reset
|
|
1100
|
+
// ============================================================================
|
|
1101
|
+
async sendResetPasswordEmail(request) {
|
|
1102
|
+
try {
|
|
1103
|
+
const response = await this.http.post("/api/auth/email/send-reset-password", request);
|
|
1104
|
+
return { data: response, error: null };
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
return wrapError(
|
|
1107
|
+
error,
|
|
1108
|
+
"An unexpected error occurred while sending password reset email"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
async exchangeResetPasswordToken(request) {
|
|
1113
|
+
try {
|
|
1114
|
+
const response = await this.http.post(
|
|
1115
|
+
"/api/auth/email/exchange-reset-password-token",
|
|
1116
|
+
request
|
|
1117
|
+
);
|
|
1118
|
+
return { data: response, error: null };
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
return wrapError(
|
|
1121
|
+
error,
|
|
1122
|
+
"An unexpected error occurred while verifying reset code"
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async resetPassword(request) {
|
|
1127
|
+
try {
|
|
1128
|
+
const response = await this.http.post(
|
|
1129
|
+
"/api/auth/email/reset-password",
|
|
1130
|
+
request
|
|
1131
|
+
);
|
|
1132
|
+
return { data: response, error: null };
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
return wrapError(
|
|
1135
|
+
error,
|
|
1136
|
+
"An unexpected error occurred while resetting password"
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
// ============================================================================
|
|
1141
|
+
// Configuration
|
|
1142
|
+
// ============================================================================
|
|
1143
|
+
async getPublicAuthConfig() {
|
|
1144
|
+
try {
|
|
1145
|
+
const response = await this.http.get(
|
|
1146
|
+
"/api/auth/public-config"
|
|
1147
|
+
);
|
|
1148
|
+
return { data: response, error: null };
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
return wrapError(
|
|
1151
|
+
error,
|
|
1152
|
+
"An unexpected error occurred while fetching auth configuration"
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
868
1156
|
};
|
|
869
1157
|
|
|
870
1158
|
// src/modules/database-postgrest.ts
|
|
@@ -873,8 +1161,10 @@ function createInsForgePostgrestFetch(httpClient, tokenManager) {
|
|
|
873
1161
|
return async (input, init) => {
|
|
874
1162
|
const url = typeof input === "string" ? input : input.toString();
|
|
875
1163
|
const urlObj = new URL(url);
|
|
876
|
-
const
|
|
877
|
-
const
|
|
1164
|
+
const pathname = urlObj.pathname.slice(1);
|
|
1165
|
+
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
1166
|
+
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
1167
|
+
const insforgeUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
878
1168
|
const token = tokenManager.getAccessToken();
|
|
879
1169
|
const httpHeaders = httpClient.getHeaders();
|
|
880
1170
|
const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
|
|
@@ -934,6 +1224,25 @@ var Database = class {
|
|
|
934
1224
|
from(table) {
|
|
935
1225
|
return this.postgrest.from(table);
|
|
936
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Call a PostgreSQL function (RPC)
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* // Call a function with parameters
|
|
1232
|
+
* const { data, error } = await client.database
|
|
1233
|
+
* .rpc('get_user_stats', { user_id: 123 });
|
|
1234
|
+
*
|
|
1235
|
+
* // Call a function with no parameters
|
|
1236
|
+
* const { data, error } = await client.database
|
|
1237
|
+
* .rpc('get_all_active_users');
|
|
1238
|
+
*
|
|
1239
|
+
* // With options (head, count, get)
|
|
1240
|
+
* const { data, count } = await client.database
|
|
1241
|
+
* .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
|
|
1242
|
+
*/
|
|
1243
|
+
rpc(fn, args, options) {
|
|
1244
|
+
return this.postgrest.rpc(fn, args, options);
|
|
1245
|
+
}
|
|
937
1246
|
};
|
|
938
1247
|
|
|
939
1248
|
// src/modules/storage.ts
|
|
@@ -1218,6 +1527,7 @@ var AI = class {
|
|
|
1218
1527
|
this.http = http;
|
|
1219
1528
|
this.chat = new Chat(http);
|
|
1220
1529
|
this.images = new Images(http);
|
|
1530
|
+
this.embeddings = new Embeddings(http);
|
|
1221
1531
|
}
|
|
1222
1532
|
};
|
|
1223
1533
|
var Chat = class {
|
|
@@ -1241,16 +1551,46 @@ var ChatCompletions = class {
|
|
|
1241
1551
|
* });
|
|
1242
1552
|
* console.log(completion.choices[0].message.content);
|
|
1243
1553
|
*
|
|
1244
|
-
* // With images
|
|
1554
|
+
* // With images (OpenAI-compatible format)
|
|
1245
1555
|
* const response = await client.ai.chat.completions.create({
|
|
1246
1556
|
* model: 'gpt-4-vision',
|
|
1247
1557
|
* messages: [{
|
|
1248
1558
|
* role: 'user',
|
|
1249
|
-
* content:
|
|
1250
|
-
*
|
|
1559
|
+
* content: [
|
|
1560
|
+
* { type: 'text', text: 'What is in this image?' },
|
|
1561
|
+
* { type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
|
|
1562
|
+
* ]
|
|
1251
1563
|
* }]
|
|
1252
1564
|
* });
|
|
1253
1565
|
*
|
|
1566
|
+
* // With PDF files
|
|
1567
|
+
* const pdfResponse = await client.ai.chat.completions.create({
|
|
1568
|
+
* model: 'anthropic/claude-3.5-sonnet',
|
|
1569
|
+
* messages: [{
|
|
1570
|
+
* role: 'user',
|
|
1571
|
+
* content: [
|
|
1572
|
+
* { type: 'text', text: 'Summarize this document' },
|
|
1573
|
+
* { type: 'file', file: { filename: 'doc.pdf', file_data: 'https://example.com/doc.pdf' } }
|
|
1574
|
+
* ]
|
|
1575
|
+
* }],
|
|
1576
|
+
* fileParser: { enabled: true, pdf: { engine: 'mistral-ocr' } }
|
|
1577
|
+
* });
|
|
1578
|
+
*
|
|
1579
|
+
* // With web search
|
|
1580
|
+
* const searchResponse = await client.ai.chat.completions.create({
|
|
1581
|
+
* model: 'openai/gpt-4',
|
|
1582
|
+
* messages: [{ role: 'user', content: 'What are the latest news about AI?' }],
|
|
1583
|
+
* webSearch: { enabled: true, maxResults: 5 }
|
|
1584
|
+
* });
|
|
1585
|
+
* // Access citations from response.choices[0].message.annotations
|
|
1586
|
+
*
|
|
1587
|
+
* // With thinking/reasoning mode (Anthropic models)
|
|
1588
|
+
* const thinkingResponse = await client.ai.chat.completions.create({
|
|
1589
|
+
* model: 'anthropic/claude-3.5-sonnet',
|
|
1590
|
+
* messages: [{ role: 'user', content: 'Solve this complex math problem...' }],
|
|
1591
|
+
* thinking: true
|
|
1592
|
+
* });
|
|
1593
|
+
*
|
|
1254
1594
|
* // Streaming - returns async iterable
|
|
1255
1595
|
* const stream = await client.ai.chat.completions.create({
|
|
1256
1596
|
* model: 'gpt-4',
|
|
@@ -1272,7 +1612,15 @@ var ChatCompletions = class {
|
|
|
1272
1612
|
temperature: params.temperature,
|
|
1273
1613
|
maxTokens: params.maxTokens,
|
|
1274
1614
|
topP: params.topP,
|
|
1275
|
-
stream: params.stream
|
|
1615
|
+
stream: params.stream,
|
|
1616
|
+
// New plugin options
|
|
1617
|
+
webSearch: params.webSearch,
|
|
1618
|
+
fileParser: params.fileParser,
|
|
1619
|
+
thinking: params.thinking,
|
|
1620
|
+
// Tool calling options
|
|
1621
|
+
tools: params.tools,
|
|
1622
|
+
toolChoice: params.toolChoice,
|
|
1623
|
+
parallelToolCalls: params.parallelToolCalls
|
|
1276
1624
|
};
|
|
1277
1625
|
if (params.stream) {
|
|
1278
1626
|
const headers = this.http.getHeaders();
|
|
@@ -1306,9 +1654,13 @@ var ChatCompletions = class {
|
|
|
1306
1654
|
index: 0,
|
|
1307
1655
|
message: {
|
|
1308
1656
|
role: "assistant",
|
|
1309
|
-
content
|
|
1657
|
+
content,
|
|
1658
|
+
// Include tool_calls if present (from tool calling)
|
|
1659
|
+
...response.tool_calls?.length && { tool_calls: response.tool_calls },
|
|
1660
|
+
// Include annotations if present (from web search or file parsing)
|
|
1661
|
+
...response.annotations?.length && { annotations: response.annotations }
|
|
1310
1662
|
},
|
|
1311
|
-
finish_reason: "stop"
|
|
1663
|
+
finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
|
|
1312
1664
|
}
|
|
1313
1665
|
],
|
|
1314
1666
|
usage: response.metadata?.usage || {
|
|
@@ -1350,7 +1702,24 @@ var ChatCompletions = class {
|
|
|
1350
1702
|
delta: {
|
|
1351
1703
|
content: data.chunk || data.content
|
|
1352
1704
|
},
|
|
1353
|
-
finish_reason:
|
|
1705
|
+
finish_reason: null
|
|
1706
|
+
}
|
|
1707
|
+
]
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
if (data.tool_calls?.length) {
|
|
1711
|
+
yield {
|
|
1712
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1713
|
+
object: "chat.completion.chunk",
|
|
1714
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1715
|
+
model,
|
|
1716
|
+
choices: [
|
|
1717
|
+
{
|
|
1718
|
+
index: 0,
|
|
1719
|
+
delta: {
|
|
1720
|
+
tool_calls: data.tool_calls
|
|
1721
|
+
},
|
|
1722
|
+
finish_reason: "tool_calls"
|
|
1354
1723
|
}
|
|
1355
1724
|
]
|
|
1356
1725
|
};
|
|
@@ -1371,6 +1740,65 @@ var ChatCompletions = class {
|
|
|
1371
1740
|
}
|
|
1372
1741
|
}
|
|
1373
1742
|
};
|
|
1743
|
+
var Embeddings = class {
|
|
1744
|
+
constructor(http) {
|
|
1745
|
+
this.http = http;
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Create embeddings for text input - OpenAI-like response format
|
|
1749
|
+
*
|
|
1750
|
+
* @example
|
|
1751
|
+
* ```typescript
|
|
1752
|
+
* // Single text input
|
|
1753
|
+
* const response = await client.ai.embeddings.create({
|
|
1754
|
+
* model: 'openai/text-embedding-3-small',
|
|
1755
|
+
* input: 'Hello world'
|
|
1756
|
+
* });
|
|
1757
|
+
* console.log(response.data[0].embedding); // number[]
|
|
1758
|
+
*
|
|
1759
|
+
* // Multiple text inputs
|
|
1760
|
+
* const response = await client.ai.embeddings.create({
|
|
1761
|
+
* model: 'openai/text-embedding-3-small',
|
|
1762
|
+
* input: ['Hello world', 'Goodbye world']
|
|
1763
|
+
* });
|
|
1764
|
+
* response.data.forEach((item, i) => {
|
|
1765
|
+
* console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
|
|
1766
|
+
* });
|
|
1767
|
+
*
|
|
1768
|
+
* // With custom dimensions (if supported by model)
|
|
1769
|
+
* const response = await client.ai.embeddings.create({
|
|
1770
|
+
* model: 'openai/text-embedding-3-small',
|
|
1771
|
+
* input: 'Hello world',
|
|
1772
|
+
* dimensions: 256
|
|
1773
|
+
* });
|
|
1774
|
+
*
|
|
1775
|
+
* // With base64 encoding format
|
|
1776
|
+
* const response = await client.ai.embeddings.create({
|
|
1777
|
+
* model: 'openai/text-embedding-3-small',
|
|
1778
|
+
* input: 'Hello world',
|
|
1779
|
+
* encoding_format: 'base64'
|
|
1780
|
+
* });
|
|
1781
|
+
* ```
|
|
1782
|
+
*/
|
|
1783
|
+
async create(params) {
|
|
1784
|
+
const response = await this.http.post(
|
|
1785
|
+
"/api/ai/embeddings",
|
|
1786
|
+
params
|
|
1787
|
+
);
|
|
1788
|
+
return {
|
|
1789
|
+
object: response.object,
|
|
1790
|
+
data: response.data,
|
|
1791
|
+
model: response.metadata?.model,
|
|
1792
|
+
usage: response.metadata?.usage ? {
|
|
1793
|
+
prompt_tokens: response.metadata.usage.promptTokens || 0,
|
|
1794
|
+
total_tokens: response.metadata.usage.totalTokens || 0
|
|
1795
|
+
} : {
|
|
1796
|
+
prompt_tokens: 0,
|
|
1797
|
+
total_tokens: 0
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1374
1802
|
var Images = class {
|
|
1375
1803
|
constructor(http) {
|
|
1376
1804
|
this.http = http;
|
|
@@ -1428,30 +1856,73 @@ var Images = class {
|
|
|
1428
1856
|
};
|
|
1429
1857
|
|
|
1430
1858
|
// src/modules/functions.ts
|
|
1431
|
-
var Functions = class {
|
|
1432
|
-
constructor(http) {
|
|
1859
|
+
var Functions = class _Functions {
|
|
1860
|
+
constructor(http, functionsUrl) {
|
|
1433
1861
|
this.http = http;
|
|
1862
|
+
this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Derive the subhosting URL from the base URL.
|
|
1866
|
+
* Base URL pattern: https://{appKey}.{region}.insforge.app
|
|
1867
|
+
* Functions URL: https://{appKey}.functions.insforge.app
|
|
1868
|
+
* Only applies to .insforge.app domains.
|
|
1869
|
+
*/
|
|
1870
|
+
static deriveSubhostingUrl(baseUrl) {
|
|
1871
|
+
try {
|
|
1872
|
+
const { hostname } = new URL(baseUrl);
|
|
1873
|
+
if (!hostname.endsWith(".insforge.app")) return void 0;
|
|
1874
|
+
const appKey = hostname.split(".")[0];
|
|
1875
|
+
return `https://${appKey}.functions.insforge.app`;
|
|
1876
|
+
} catch {
|
|
1877
|
+
return void 0;
|
|
1878
|
+
}
|
|
1434
1879
|
}
|
|
1435
1880
|
/**
|
|
1436
1881
|
* Invokes an Edge Function
|
|
1882
|
+
*
|
|
1883
|
+
* If functionsUrl is configured, tries direct subhosting first.
|
|
1884
|
+
* Falls back to proxy URL if subhosting returns 404.
|
|
1885
|
+
*
|
|
1437
1886
|
* @param slug The function slug to invoke
|
|
1438
1887
|
* @param options Request options
|
|
1439
1888
|
*/
|
|
1440
1889
|
async invoke(slug, options = {}) {
|
|
1890
|
+
const { method = "POST", body, headers = {} } = options;
|
|
1891
|
+
if (this.functionsUrl) {
|
|
1892
|
+
try {
|
|
1893
|
+
const data = await this.http.request(method, `${this.functionsUrl}/${slug}`, {
|
|
1894
|
+
body,
|
|
1895
|
+
headers
|
|
1896
|
+
});
|
|
1897
|
+
return { data, error: null };
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
1900
|
+
if (error instanceof InsForgeError && error.statusCode === 404) {
|
|
1901
|
+
} else {
|
|
1902
|
+
return {
|
|
1903
|
+
data: null,
|
|
1904
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1905
|
+
error instanceof Error ? error.message : "Function invocation failed",
|
|
1906
|
+
500,
|
|
1907
|
+
"FUNCTION_ERROR"
|
|
1908
|
+
)
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1441
1913
|
try {
|
|
1442
|
-
const { method = "POST", body, headers = {} } = options;
|
|
1443
1914
|
const path = `/functions/${slug}`;
|
|
1444
|
-
const data = await this.http.request(
|
|
1445
|
-
method,
|
|
1446
|
-
path,
|
|
1447
|
-
{ body, headers }
|
|
1448
|
-
);
|
|
1915
|
+
const data = await this.http.request(method, path, { body, headers });
|
|
1449
1916
|
return { data, error: null };
|
|
1450
1917
|
} catch (error) {
|
|
1918
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
1451
1919
|
return {
|
|
1452
1920
|
data: null,
|
|
1453
|
-
error
|
|
1454
|
-
|
|
1921
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1922
|
+
error instanceof Error ? error.message : "Function invocation failed",
|
|
1923
|
+
500,
|
|
1924
|
+
"FUNCTION_ERROR"
|
|
1925
|
+
)
|
|
1455
1926
|
};
|
|
1456
1927
|
}
|
|
1457
1928
|
}
|
|
@@ -1461,13 +1932,15 @@ var Functions = class {
|
|
|
1461
1932
|
import { io } from "socket.io-client";
|
|
1462
1933
|
var CONNECT_TIMEOUT = 1e4;
|
|
1463
1934
|
var Realtime = class {
|
|
1464
|
-
constructor(baseUrl, tokenManager) {
|
|
1935
|
+
constructor(baseUrl, tokenManager, anonKey) {
|
|
1465
1936
|
this.socket = null;
|
|
1466
1937
|
this.connectPromise = null;
|
|
1467
1938
|
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
1468
1939
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
1469
1940
|
this.baseUrl = baseUrl;
|
|
1470
1941
|
this.tokenManager = tokenManager;
|
|
1942
|
+
this.anonKey = anonKey;
|
|
1943
|
+
this.tokenManager.onTokenChange = () => this.onTokenChange();
|
|
1471
1944
|
}
|
|
1472
1945
|
notifyListeners(event, payload) {
|
|
1473
1946
|
const listeners = this.eventListeners.get(event);
|
|
@@ -1492,8 +1965,7 @@ var Realtime = class {
|
|
|
1492
1965
|
return this.connectPromise;
|
|
1493
1966
|
}
|
|
1494
1967
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
1495
|
-
const
|
|
1496
|
-
const token = session?.accessToken;
|
|
1968
|
+
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
1497
1969
|
this.socket = io(this.baseUrl, {
|
|
1498
1970
|
transports: ["websocket"],
|
|
1499
1971
|
auth: token ? { token } : void 0
|
|
@@ -1559,6 +2031,21 @@ var Realtime = class {
|
|
|
1559
2031
|
}
|
|
1560
2032
|
this.subscribedChannels.clear();
|
|
1561
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Handle token changes (e.g., after auth refresh)
|
|
2036
|
+
* Updates socket auth so reconnects use the new token
|
|
2037
|
+
* If connected, triggers reconnect to apply new token immediately
|
|
2038
|
+
*/
|
|
2039
|
+
onTokenChange() {
|
|
2040
|
+
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
2041
|
+
if (this.socket) {
|
|
2042
|
+
this.socket.auth = token ? { token } : {};
|
|
2043
|
+
}
|
|
2044
|
+
if (this.socket && (this.socket.connected || this.connectPromise)) {
|
|
2045
|
+
this.socket.disconnect();
|
|
2046
|
+
this.socket.connect();
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
1562
2049
|
/**
|
|
1563
2050
|
* Check if connected to the realtime server
|
|
1564
2051
|
*/
|
|
@@ -1707,8 +2194,15 @@ var Emails = class {
|
|
|
1707
2194
|
);
|
|
1708
2195
|
return { data, error: null };
|
|
1709
2196
|
} catch (error) {
|
|
1710
|
-
|
|
1711
|
-
return {
|
|
2197
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
2198
|
+
return {
|
|
2199
|
+
data: null,
|
|
2200
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2201
|
+
error instanceof Error ? error.message : "Email send failed",
|
|
2202
|
+
500,
|
|
2203
|
+
"EMAIL_ERROR"
|
|
2204
|
+
)
|
|
2205
|
+
};
|
|
1712
2206
|
}
|
|
1713
2207
|
}
|
|
1714
2208
|
};
|
|
@@ -1716,31 +2210,30 @@ var Emails = class {
|
|
|
1716
2210
|
// src/client.ts
|
|
1717
2211
|
var InsForgeClient = class {
|
|
1718
2212
|
constructor(config = {}) {
|
|
1719
|
-
|
|
1720
|
-
this.tokenManager = new TokenManager(
|
|
2213
|
+
const logger = new Logger(config.debug);
|
|
2214
|
+
this.tokenManager = new TokenManager();
|
|
2215
|
+
this.http = new HttpClient(config, this.tokenManager, logger);
|
|
1721
2216
|
if (config.edgeFunctionToken) {
|
|
1722
2217
|
this.http.setAuthToken(config.edgeFunctionToken);
|
|
1723
|
-
this.tokenManager.
|
|
1724
|
-
accessToken: config.edgeFunctionToken,
|
|
1725
|
-
user: {}
|
|
1726
|
-
// Will be populated by getCurrentUser()
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
const existingSession = this.tokenManager.getSession();
|
|
1730
|
-
if (existingSession?.accessToken) {
|
|
1731
|
-
this.http.setAuthToken(existingSession.accessToken);
|
|
2218
|
+
this.tokenManager.setAccessToken(config.edgeFunctionToken);
|
|
1732
2219
|
}
|
|
1733
|
-
this.auth = new Auth(this.http, this.tokenManager
|
|
2220
|
+
this.auth = new Auth(this.http, this.tokenManager, {
|
|
2221
|
+
isServerMode: config.isServerMode ?? false
|
|
2222
|
+
});
|
|
1734
2223
|
this.database = new Database(this.http, this.tokenManager);
|
|
1735
2224
|
this.storage = new Storage(this.http);
|
|
1736
2225
|
this.ai = new AI(this.http);
|
|
1737
|
-
this.functions = new Functions(this.http);
|
|
1738
|
-
this.realtime = new Realtime(
|
|
2226
|
+
this.functions = new Functions(this.http, config.functionsUrl);
|
|
2227
|
+
this.realtime = new Realtime(
|
|
2228
|
+
this.http.baseUrl,
|
|
2229
|
+
this.tokenManager,
|
|
2230
|
+
config.anonKey
|
|
2231
|
+
);
|
|
1739
2232
|
this.emails = new Emails(this.http);
|
|
1740
2233
|
}
|
|
1741
2234
|
/**
|
|
1742
2235
|
* Get the underlying HTTP client for custom requests
|
|
1743
|
-
*
|
|
2236
|
+
*
|
|
1744
2237
|
* @example
|
|
1745
2238
|
* ```typescript
|
|
1746
2239
|
* const httpClient = client.getHttpClient();
|
|
@@ -1774,6 +2267,7 @@ export {
|
|
|
1774
2267
|
HttpClient,
|
|
1775
2268
|
InsForgeClient,
|
|
1776
2269
|
InsForgeError,
|
|
2270
|
+
Logger,
|
|
1777
2271
|
Realtime,
|
|
1778
2272
|
Storage,
|
|
1779
2273
|
StorageBucket,
|