vibeusage 0.2.23 → 0.3.1
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 +25 -20
- package/README.zh-CN.md +7 -2
- 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 +5 -6
- package/src/cli.js +2 -2
- package/src/commands/init.js +20 -362
- package/src/commands/status.js +58 -51
- package/src/commands/sync.js +37 -25
- package/src/commands/uninstall.js +121 -104
- package/src/lib/claude-config.js +130 -35
- package/src/lib/diagnostics.js +88 -57
- package/src/lib/doctor.js +50 -0
- package/src/lib/insforge-client.js +13 -9
- package/src/lib/integrations/claude.js +106 -0
- package/src/lib/integrations/codex.js +88 -0
- package/src/lib/integrations/context.js +76 -0
- package/src/lib/integrations/every-code.js +88 -0
- package/src/lib/integrations/gemini.js +86 -0
- package/src/lib/integrations/index.js +85 -0
- package/src/lib/integrations/openclaw-legacy.js +123 -0
- package/src/lib/integrations/openclaw-session.js +132 -0
- package/src/lib/integrations/opencode.js +86 -0
- package/src/lib/integrations/utils.js +39 -0
- package/src/lib/opencode-sqlite.js +113 -0
- package/src/lib/opencode-usage-audit.js +3 -2
- package/src/lib/rollout.js +227 -1
- package/src/lib/runtime-config.js +7 -5
- package/src/lib/vibeusage-api.js +11 -7
- package/src/shared/copy-registry.cjs +142 -0
- package/src/shared/copy-registry.cjs.d.ts +33 -0
- package/src/shared/runtime-defaults.cjs +11 -0
- package/src/shared/runtime-defaults.cjs.d.ts +3 -0
- package/src/shared/vibeusage-function-contract.cjs +34 -0
- package/src/shared/vibeusage-function-contract.cjs.d.ts +4 -0
- package/src/commands/activate-if-needed.js +0 -41
- package/src/lib/activation-check.js +0 -341
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
HttpClient: () => HttpClient,
|
|
29
29
|
InsForgeClient: () => InsForgeClient,
|
|
30
30
|
InsForgeError: () => InsForgeError,
|
|
31
|
+
Logger: () => Logger,
|
|
31
32
|
Realtime: () => Realtime,
|
|
32
33
|
Storage: () => Storage,
|
|
33
34
|
StorageBucket: () => StorageBucket,
|
|
@@ -56,127 +57,182 @@ var InsForgeError = class _InsForgeError extends Error {
|
|
|
56
57
|
}
|
|
57
58
|
};
|
|
58
59
|
|
|
59
|
-
// src/lib/
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
// src/lib/logger.ts
|
|
61
|
+
var SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie", "set-cookie"];
|
|
62
|
+
var SENSITIVE_BODY_KEYS = [
|
|
63
|
+
"password",
|
|
64
|
+
"token",
|
|
65
|
+
"accesstoken",
|
|
66
|
+
"refreshtoken",
|
|
67
|
+
"authorization",
|
|
68
|
+
"secret",
|
|
69
|
+
"apikey",
|
|
70
|
+
"api_key",
|
|
71
|
+
"email",
|
|
72
|
+
"ssn",
|
|
73
|
+
"creditcard",
|
|
74
|
+
"credit_card"
|
|
75
|
+
];
|
|
76
|
+
function redactHeaders(headers) {
|
|
77
|
+
const redacted = {};
|
|
78
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
79
|
+
if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
|
|
80
|
+
redacted[key] = "***REDACTED***";
|
|
81
|
+
} else {
|
|
82
|
+
redacted[key] = value;
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
});
|
|
85
|
+
return redacted;
|
|
86
|
+
}
|
|
87
|
+
function sanitizeBody(body) {
|
|
88
|
+
if (body === null || body === void 0) return body;
|
|
89
|
+
if (typeof body === "string") {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(body);
|
|
92
|
+
return sanitizeBody(parsed);
|
|
93
|
+
} catch {
|
|
94
|
+
return body;
|
|
87
95
|
}
|
|
88
|
-
return url.toString();
|
|
89
96
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const authToken = this.userToken || this.anonKey;
|
|
97
|
-
if (authToken) {
|
|
98
|
-
requestHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
99
|
-
}
|
|
100
|
-
let processedBody;
|
|
101
|
-
if (body !== void 0) {
|
|
102
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
103
|
-
processedBody = body;
|
|
97
|
+
if (Array.isArray(body)) return body.map(sanitizeBody);
|
|
98
|
+
if (typeof body === "object") {
|
|
99
|
+
const sanitized = {};
|
|
100
|
+
for (const [key, value] of Object.entries(body)) {
|
|
101
|
+
if (SENSITIVE_BODY_KEYS.includes(key.toLowerCase().replace(/[-_]/g, ""))) {
|
|
102
|
+
sanitized[key] = "***REDACTED***";
|
|
104
103
|
} else {
|
|
105
|
-
|
|
106
|
-
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
107
|
-
}
|
|
108
|
-
processedBody = JSON.stringify(body);
|
|
104
|
+
sanitized[key] = sanitizeBody(value);
|
|
109
105
|
}
|
|
110
106
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const contentType = response.headers.get("content-type");
|
|
123
|
-
if (contentType?.includes("json")) {
|
|
124
|
-
data = await response.json();
|
|
125
|
-
} else {
|
|
126
|
-
data = await response.text();
|
|
127
|
-
}
|
|
128
|
-
if (!response.ok) {
|
|
129
|
-
if (data && typeof data === "object" && "error" in data) {
|
|
130
|
-
if (!data.statusCode && !data.status) {
|
|
131
|
-
data.statusCode = response.status;
|
|
132
|
-
}
|
|
133
|
-
const error = InsForgeError.fromApiError(data);
|
|
134
|
-
Object.keys(data).forEach((key) => {
|
|
135
|
-
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
136
|
-
error[key] = data[key];
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
141
|
-
throw new InsForgeError(
|
|
142
|
-
`Request failed: ${response.statusText}`,
|
|
143
|
-
response.status,
|
|
144
|
-
"REQUEST_FAILED"
|
|
145
|
-
);
|
|
107
|
+
return sanitized;
|
|
108
|
+
}
|
|
109
|
+
return body;
|
|
110
|
+
}
|
|
111
|
+
function formatBody(body) {
|
|
112
|
+
if (body === void 0 || body === null) return "";
|
|
113
|
+
if (typeof body === "string") {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.stringify(JSON.parse(body), null, 2);
|
|
116
|
+
} catch {
|
|
117
|
+
return body;
|
|
146
118
|
}
|
|
147
|
-
return data;
|
|
148
119
|
}
|
|
149
|
-
|
|
150
|
-
return
|
|
120
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
121
|
+
return "[FormData]";
|
|
151
122
|
}
|
|
152
|
-
|
|
153
|
-
return
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(body, null, 2);
|
|
125
|
+
} catch {
|
|
126
|
+
return "[Unserializable body]";
|
|
154
127
|
}
|
|
155
|
-
|
|
156
|
-
|
|
128
|
+
}
|
|
129
|
+
var Logger = class {
|
|
130
|
+
/**
|
|
131
|
+
* Creates a new Logger instance.
|
|
132
|
+
* @param debug - Set to true to enable console logging, or pass a custom log function
|
|
133
|
+
*/
|
|
134
|
+
constructor(debug) {
|
|
135
|
+
if (typeof debug === "function") {
|
|
136
|
+
this.enabled = true;
|
|
137
|
+
this.customLog = debug;
|
|
138
|
+
} else {
|
|
139
|
+
this.enabled = !!debug;
|
|
140
|
+
this.customLog = null;
|
|
141
|
+
}
|
|
157
142
|
}
|
|
158
|
-
|
|
159
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Logs a debug message at the info level.
|
|
145
|
+
* @param message - The message to log
|
|
146
|
+
* @param args - Additional arguments to pass to the log function
|
|
147
|
+
*/
|
|
148
|
+
log(message, ...args) {
|
|
149
|
+
if (!this.enabled) return;
|
|
150
|
+
const formatted = `[InsForge Debug] ${message}`;
|
|
151
|
+
if (this.customLog) {
|
|
152
|
+
this.customLog(formatted, ...args);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(formatted, ...args);
|
|
155
|
+
}
|
|
160
156
|
}
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Logs a debug message at the warning level.
|
|
159
|
+
* @param message - The message to log
|
|
160
|
+
* @param args - Additional arguments to pass to the log function
|
|
161
|
+
*/
|
|
162
|
+
warn(message, ...args) {
|
|
163
|
+
if (!this.enabled) return;
|
|
164
|
+
const formatted = `[InsForge Debug] ${message}`;
|
|
165
|
+
if (this.customLog) {
|
|
166
|
+
this.customLog(formatted, ...args);
|
|
167
|
+
} else {
|
|
168
|
+
console.warn(formatted, ...args);
|
|
169
|
+
}
|
|
163
170
|
}
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Logs a debug message at the error level.
|
|
173
|
+
* @param message - The message to log
|
|
174
|
+
* @param args - Additional arguments to pass to the log function
|
|
175
|
+
*/
|
|
176
|
+
error(message, ...args) {
|
|
177
|
+
if (!this.enabled) return;
|
|
178
|
+
const formatted = `[InsForge Debug] ${message}`;
|
|
179
|
+
if (this.customLog) {
|
|
180
|
+
this.customLog(formatted, ...args);
|
|
181
|
+
} else {
|
|
182
|
+
console.error(formatted, ...args);
|
|
183
|
+
}
|
|
166
184
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Logs an outgoing HTTP request with method, URL, headers, and body.
|
|
187
|
+
* Sensitive headers and body fields are automatically redacted.
|
|
188
|
+
* @param method - HTTP method (GET, POST, etc.)
|
|
189
|
+
* @param url - The full request URL
|
|
190
|
+
* @param headers - Request headers (sensitive values will be redacted)
|
|
191
|
+
* @param body - Request body (sensitive fields will be masked)
|
|
192
|
+
*/
|
|
193
|
+
logRequest(method, url, headers, body) {
|
|
194
|
+
if (!this.enabled) return;
|
|
195
|
+
const parts = [
|
|
196
|
+
`\u2192 ${method} ${url}`
|
|
197
|
+
];
|
|
198
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
199
|
+
parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
|
|
200
|
+
}
|
|
201
|
+
const formattedBody = formatBody(sanitizeBody(body));
|
|
202
|
+
if (formattedBody) {
|
|
203
|
+
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
204
|
+
parts.push(` Body: ${truncated}`);
|
|
205
|
+
}
|
|
206
|
+
this.log(parts.join("\n"));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Logs an incoming HTTP response with method, URL, status, duration, and body.
|
|
210
|
+
* Error responses (4xx/5xx) are logged at the error level.
|
|
211
|
+
* @param method - HTTP method (GET, POST, etc.)
|
|
212
|
+
* @param url - The full request URL
|
|
213
|
+
* @param status - HTTP response status code
|
|
214
|
+
* @param durationMs - Request duration in milliseconds
|
|
215
|
+
* @param body - Response body (sensitive fields will be masked, large bodies truncated)
|
|
216
|
+
*/
|
|
217
|
+
logResponse(method, url, status, durationMs, body) {
|
|
218
|
+
if (!this.enabled) return;
|
|
219
|
+
const parts = [
|
|
220
|
+
`\u2190 ${method} ${url} ${status} (${durationMs}ms)`
|
|
221
|
+
];
|
|
222
|
+
const formattedBody = formatBody(sanitizeBody(body));
|
|
223
|
+
if (formattedBody) {
|
|
224
|
+
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
225
|
+
parts.push(` Body: ${truncated}`);
|
|
226
|
+
}
|
|
227
|
+
if (status >= 400) {
|
|
228
|
+
this.error(parts.join("\n"));
|
|
229
|
+
} else {
|
|
230
|
+
this.log(parts.join("\n"));
|
|
172
231
|
}
|
|
173
|
-
return headers;
|
|
174
232
|
}
|
|
175
233
|
};
|
|
176
234
|
|
|
177
235
|
// src/lib/token-manager.ts
|
|
178
|
-
var TOKEN_KEY = "insforge-auth-token";
|
|
179
|
-
var USER_KEY = "insforge-auth-user";
|
|
180
236
|
var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
|
|
181
237
|
function getCsrfToken() {
|
|
182
238
|
if (typeof document === "undefined") return null;
|
|
@@ -196,84 +252,28 @@ function clearCsrfToken() {
|
|
|
196
252
|
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
197
253
|
}
|
|
198
254
|
var TokenManager = class {
|
|
199
|
-
constructor(
|
|
255
|
+
constructor() {
|
|
200
256
|
// In-memory storage
|
|
201
257
|
this.accessToken = null;
|
|
202
258
|
this.user = null;
|
|
203
|
-
//
|
|
204
|
-
this.
|
|
205
|
-
if (storage) {
|
|
206
|
-
this.storage = storage;
|
|
207
|
-
} else if (typeof window !== "undefined" && window.localStorage) {
|
|
208
|
-
this.storage = window.localStorage;
|
|
209
|
-
} else {
|
|
210
|
-
const store = /* @__PURE__ */ new Map();
|
|
211
|
-
this.storage = {
|
|
212
|
-
getItem: (key) => store.get(key) || null,
|
|
213
|
-
setItem: (key, value) => {
|
|
214
|
-
store.set(key, value);
|
|
215
|
-
},
|
|
216
|
-
removeItem: (key) => {
|
|
217
|
-
store.delete(key);
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Get current mode
|
|
224
|
-
*/
|
|
225
|
-
get mode() {
|
|
226
|
-
return this._mode;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Set mode to memory (new backend with cookies + memory)
|
|
230
|
-
*/
|
|
231
|
-
setMemoryMode() {
|
|
232
|
-
if (this._mode === "storage") {
|
|
233
|
-
this.storage.removeItem(TOKEN_KEY);
|
|
234
|
-
this.storage.removeItem(USER_KEY);
|
|
235
|
-
}
|
|
236
|
-
this._mode = "memory";
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Set mode to storage (legacy backend with localStorage)
|
|
240
|
-
* Also loads existing session from localStorage
|
|
241
|
-
*/
|
|
242
|
-
setStorageMode() {
|
|
243
|
-
this._mode = "storage";
|
|
244
|
-
this.loadFromStorage();
|
|
259
|
+
// Callback for token changes (used by realtime to reconnect with new token)
|
|
260
|
+
this.onTokenChange = null;
|
|
245
261
|
}
|
|
246
262
|
/**
|
|
247
|
-
*
|
|
248
|
-
*/
|
|
249
|
-
loadFromStorage() {
|
|
250
|
-
const token = this.storage.getItem(TOKEN_KEY);
|
|
251
|
-
const userStr = this.storage.getItem(USER_KEY);
|
|
252
|
-
if (token && userStr) {
|
|
253
|
-
try {
|
|
254
|
-
this.accessToken = token;
|
|
255
|
-
this.user = JSON.parse(userStr);
|
|
256
|
-
} catch {
|
|
257
|
-
this.clearSession();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Save session (memory always, localStorage only in storage mode)
|
|
263
|
+
* Save session in memory
|
|
263
264
|
*/
|
|
264
265
|
saveSession(session) {
|
|
266
|
+
const tokenChanged = session.accessToken !== this.accessToken;
|
|
265
267
|
this.accessToken = session.accessToken;
|
|
266
268
|
this.user = session.user;
|
|
267
|
-
if (this.
|
|
268
|
-
this.
|
|
269
|
-
this.storage.setItem(USER_KEY, JSON.stringify(session.user));
|
|
269
|
+
if (tokenChanged && this.onTokenChange) {
|
|
270
|
+
this.onTokenChange();
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
/**
|
|
273
274
|
* Get current session
|
|
274
275
|
*/
|
|
275
276
|
getSession() {
|
|
276
|
-
this.loadFromStorage();
|
|
277
277
|
if (!this.accessToken || !this.user) return null;
|
|
278
278
|
return {
|
|
279
279
|
accessToken: this.accessToken,
|
|
@@ -284,16 +284,16 @@ var TokenManager = class {
|
|
|
284
284
|
* Get access token
|
|
285
285
|
*/
|
|
286
286
|
getAccessToken() {
|
|
287
|
-
this.loadFromStorage();
|
|
288
287
|
return this.accessToken;
|
|
289
288
|
}
|
|
290
289
|
/**
|
|
291
290
|
* Set access token
|
|
292
291
|
*/
|
|
293
292
|
setAccessToken(token) {
|
|
293
|
+
const tokenChanged = token !== this.accessToken;
|
|
294
294
|
this.accessToken = token;
|
|
295
|
-
if (this.
|
|
296
|
-
this.
|
|
295
|
+
if (tokenChanged && this.onTokenChange) {
|
|
296
|
+
this.onTokenChange();
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
/**
|
|
@@ -307,479 +307,599 @@ var TokenManager = class {
|
|
|
307
307
|
*/
|
|
308
308
|
setUser(user) {
|
|
309
309
|
this.user = user;
|
|
310
|
-
if (this._mode === "storage") {
|
|
311
|
-
this.storage.setItem(USER_KEY, JSON.stringify(user));
|
|
312
|
-
}
|
|
313
310
|
}
|
|
314
311
|
/**
|
|
315
|
-
* Clear
|
|
312
|
+
* Clear in-memory session
|
|
316
313
|
*/
|
|
317
314
|
clearSession() {
|
|
315
|
+
const hadToken = this.accessToken !== null;
|
|
318
316
|
this.accessToken = null;
|
|
319
317
|
this.user = null;
|
|
320
|
-
this.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Check if there's a session in localStorage (for legacy detection)
|
|
325
|
-
*/
|
|
326
|
-
hasStoredSession() {
|
|
327
|
-
const token = this.storage.getItem(TOKEN_KEY);
|
|
328
|
-
return !!token;
|
|
318
|
+
if (hadToken && this.onTokenChange) {
|
|
319
|
+
this.onTokenChange();
|
|
320
|
+
}
|
|
329
321
|
}
|
|
330
322
|
};
|
|
331
323
|
|
|
332
|
-
// src/
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
const { hostname, port, protocol } = window.location;
|
|
338
|
-
if (hostname === "localhost" && port === "7130") {
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
if (protocol === "https:" && hostname.endsWith(".insforge.app")) {
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
var Auth = class {
|
|
347
|
-
constructor(http, tokenManager) {
|
|
348
|
-
this.http = http;
|
|
349
|
-
this.tokenManager = tokenManager;
|
|
350
|
-
this.detectAuthCallback();
|
|
351
|
-
}
|
|
324
|
+
// src/lib/http-client.ts
|
|
325
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
326
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
327
|
+
var HttpClient = class {
|
|
352
328
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
329
|
+
* Creates a new HttpClient instance.
|
|
330
|
+
* @param config - SDK configuration including baseUrl, timeout, retry settings, and fetch implementation.
|
|
331
|
+
* @param tokenManager - Token manager for session persistence.
|
|
332
|
+
* @param logger - Optional logger instance for request/response debugging.
|
|
356
333
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// They'll be populated when calling getCurrentUser()
|
|
380
|
-
emailVerified: false,
|
|
381
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
382
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
this.tokenManager.saveSession(session);
|
|
386
|
-
this.http.setAuthToken(accessToken);
|
|
387
|
-
const url = new URL(window.location.href);
|
|
388
|
-
url.searchParams.delete("access_token");
|
|
389
|
-
url.searchParams.delete("user_id");
|
|
390
|
-
url.searchParams.delete("email");
|
|
391
|
-
url.searchParams.delete("name");
|
|
392
|
-
url.searchParams.delete("csrf_token");
|
|
393
|
-
if (params.has("error")) {
|
|
394
|
-
url.searchParams.delete("error");
|
|
395
|
-
}
|
|
396
|
-
window.history.replaceState({}, document.title, url.toString());
|
|
397
|
-
}
|
|
398
|
-
} catch (error) {
|
|
399
|
-
console.debug("OAuth callback detection skipped:", error);
|
|
334
|
+
constructor(config, tokenManager, logger) {
|
|
335
|
+
this.userToken = null;
|
|
336
|
+
this.autoRefreshToken = true;
|
|
337
|
+
this.isRefreshing = false;
|
|
338
|
+
this.refreshPromise = null;
|
|
339
|
+
this.refreshToken = null;
|
|
340
|
+
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
341
|
+
this.autoRefreshToken = config.autoRefreshToken ?? true;
|
|
342
|
+
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
343
|
+
this.anonKey = config.anonKey;
|
|
344
|
+
this.defaultHeaders = {
|
|
345
|
+
...config.headers
|
|
346
|
+
};
|
|
347
|
+
this.tokenManager = tokenManager ?? new TokenManager();
|
|
348
|
+
this.logger = logger || new Logger(false);
|
|
349
|
+
this.timeout = config.timeout ?? 3e4;
|
|
350
|
+
this.retryCount = config.retryCount ?? 3;
|
|
351
|
+
this.retryDelay = config.retryDelay ?? 500;
|
|
352
|
+
if (!this.fetch) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
"Fetch is not available. Please provide a fetch implementation in the config."
|
|
355
|
+
);
|
|
400
356
|
}
|
|
401
357
|
}
|
|
402
358
|
/**
|
|
403
|
-
*
|
|
359
|
+
* Builds a full URL from a path and optional query parameters.
|
|
360
|
+
* Normalizes PostgREST select parameters for proper syntax.
|
|
404
361
|
*/
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (response.csrfToken) {
|
|
416
|
-
setCsrfToken(response.csrfToken);
|
|
362
|
+
buildUrl(path, params) {
|
|
363
|
+
const url = new URL(path, this.baseUrl);
|
|
364
|
+
if (params) {
|
|
365
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
366
|
+
if (key === "select") {
|
|
367
|
+
let normalizedValue = value.replace(/\s+/g, " ").trim();
|
|
368
|
+
normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
|
|
369
|
+
url.searchParams.append(key, normalizedValue);
|
|
370
|
+
} else {
|
|
371
|
+
url.searchParams.append(key, value);
|
|
417
372
|
}
|
|
418
|
-
}
|
|
419
|
-
return {
|
|
420
|
-
data: response,
|
|
421
|
-
error: null
|
|
422
|
-
};
|
|
423
|
-
} catch (error) {
|
|
424
|
-
if (error instanceof InsForgeError) {
|
|
425
|
-
return { data: null, error };
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
data: null,
|
|
429
|
-
error: new InsForgeError(
|
|
430
|
-
error instanceof Error ? error.message : "An unexpected error occurred during sign up",
|
|
431
|
-
500,
|
|
432
|
-
"UNEXPECTED_ERROR"
|
|
433
|
-
)
|
|
434
|
-
};
|
|
373
|
+
});
|
|
435
374
|
}
|
|
375
|
+
return url.toString();
|
|
376
|
+
}
|
|
377
|
+
/** Checks if an HTTP status code is eligible for retry (5xx server errors). */
|
|
378
|
+
isRetryableStatus(status) {
|
|
379
|
+
return RETRYABLE_STATUS_CODES.has(status);
|
|
436
380
|
}
|
|
437
381
|
/**
|
|
438
|
-
*
|
|
382
|
+
* Computes the delay before the next retry using exponential backoff with jitter.
|
|
383
|
+
* @param attempt - The current retry attempt number (1-based).
|
|
384
|
+
* @returns Delay in milliseconds.
|
|
439
385
|
*/
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const session = {
|
|
445
|
-
accessToken: response.accessToken,
|
|
446
|
-
user: response.user
|
|
447
|
-
};
|
|
448
|
-
this.tokenManager.saveSession(session);
|
|
449
|
-
this.http.setAuthToken(response.accessToken);
|
|
450
|
-
if (response.csrfToken) {
|
|
451
|
-
setCsrfToken(response.csrfToken);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return {
|
|
455
|
-
data: response,
|
|
456
|
-
error: null
|
|
457
|
-
};
|
|
458
|
-
} catch (error) {
|
|
459
|
-
if (error instanceof InsForgeError) {
|
|
460
|
-
return { data: null, error };
|
|
461
|
-
}
|
|
462
|
-
return {
|
|
463
|
-
data: null,
|
|
464
|
-
error: new InsForgeError(
|
|
465
|
-
"An unexpected error occurred during sign in",
|
|
466
|
-
500,
|
|
467
|
-
"UNEXPECTED_ERROR"
|
|
468
|
-
)
|
|
469
|
-
};
|
|
470
|
-
}
|
|
386
|
+
computeRetryDelay(attempt) {
|
|
387
|
+
const base = this.retryDelay * Math.pow(2, attempt - 1);
|
|
388
|
+
const jitter = base * (0.85 + Math.random() * 0.3);
|
|
389
|
+
return Math.round(jitter);
|
|
471
390
|
}
|
|
472
391
|
/**
|
|
473
|
-
*
|
|
392
|
+
* Performs an HTTP request with automatic retry and timeout handling.
|
|
393
|
+
* Retries on network errors and 5xx server errors with exponential backoff.
|
|
394
|
+
* Client errors (4xx) and timeouts are thrown immediately without retry.
|
|
395
|
+
* @param method - HTTP method (GET, POST, PUT, PATCH, DELETE).
|
|
396
|
+
* @param path - API path relative to the base URL.
|
|
397
|
+
* @param options - Optional request configuration including headers, body, and query params.
|
|
398
|
+
* @returns Parsed response data.
|
|
399
|
+
* @throws {InsForgeError} On timeout, network failure, or HTTP error responses.
|
|
474
400
|
*/
|
|
475
|
-
async
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
401
|
+
async handleRequest(method, path, options = {}) {
|
|
402
|
+
const {
|
|
403
|
+
params,
|
|
404
|
+
headers = {},
|
|
405
|
+
body,
|
|
406
|
+
signal: callerSignal,
|
|
407
|
+
...fetchOptions
|
|
408
|
+
} = options;
|
|
409
|
+
const url = this.buildUrl(path, params);
|
|
410
|
+
const startTime = Date.now();
|
|
411
|
+
const canRetry = IDEMPOTENT_METHODS.has(method.toUpperCase()) || options.idempotent === true;
|
|
412
|
+
const maxAttempts = canRetry ? this.retryCount : 0;
|
|
413
|
+
const requestHeaders = {
|
|
414
|
+
...this.defaultHeaders
|
|
415
|
+
};
|
|
416
|
+
const authToken = this.userToken || this.anonKey;
|
|
417
|
+
if (authToken) {
|
|
418
|
+
requestHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
419
|
+
}
|
|
420
|
+
let processedBody;
|
|
421
|
+
if (body !== void 0) {
|
|
422
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
423
|
+
processedBody = body;
|
|
424
|
+
} else {
|
|
425
|
+
if (method !== "GET") {
|
|
426
|
+
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
427
|
+
}
|
|
428
|
+
processedBody = JSON.stringify(body);
|
|
495
429
|
}
|
|
496
|
-
return {
|
|
497
|
-
data: {},
|
|
498
|
-
error: new InsForgeError(
|
|
499
|
-
"An unexpected error occurred during OAuth initialization",
|
|
500
|
-
500,
|
|
501
|
-
"UNEXPECTED_ERROR"
|
|
502
|
-
)
|
|
503
|
-
};
|
|
504
430
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
431
|
+
if (headers instanceof Headers) {
|
|
432
|
+
headers.forEach((value, key) => {
|
|
433
|
+
requestHeaders[key] = value;
|
|
434
|
+
});
|
|
435
|
+
} else if (Array.isArray(headers)) {
|
|
436
|
+
headers.forEach(([key, value]) => {
|
|
437
|
+
requestHeaders[key] = value;
|
|
438
|
+
});
|
|
439
|
+
} else {
|
|
440
|
+
Object.assign(requestHeaders, headers);
|
|
441
|
+
}
|
|
442
|
+
this.logger.logRequest(method, url, requestHeaders, processedBody);
|
|
443
|
+
let lastError;
|
|
444
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
445
|
+
if (attempt > 0) {
|
|
446
|
+
const delay = this.computeRetryDelay(attempt);
|
|
447
|
+
this.logger.warn(
|
|
448
|
+
`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
|
|
449
|
+
);
|
|
450
|
+
if (callerSignal?.aborted) throw callerSignal.reason;
|
|
451
|
+
await new Promise((resolve, reject) => {
|
|
452
|
+
const onAbort = () => {
|
|
453
|
+
clearTimeout(timer2);
|
|
454
|
+
reject(callerSignal.reason);
|
|
455
|
+
};
|
|
456
|
+
const timer2 = setTimeout(() => {
|
|
457
|
+
if (callerSignal)
|
|
458
|
+
callerSignal.removeEventListener("abort", onAbort);
|
|
459
|
+
resolve();
|
|
460
|
+
}, delay);
|
|
461
|
+
if (callerSignal) {
|
|
462
|
+
callerSignal.addEventListener("abort", onAbort, { once: true });
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
let controller;
|
|
467
|
+
let timer;
|
|
468
|
+
if (this.timeout > 0 || callerSignal) {
|
|
469
|
+
controller = new AbortController();
|
|
470
|
+
if (this.timeout > 0) {
|
|
471
|
+
timer = setTimeout(() => controller.abort(), this.timeout);
|
|
472
|
+
}
|
|
473
|
+
if (callerSignal) {
|
|
474
|
+
if (callerSignal.aborted) {
|
|
475
|
+
controller.abort(callerSignal.reason);
|
|
476
|
+
} else {
|
|
477
|
+
const onCallerAbort = () => controller.abort(callerSignal.reason);
|
|
478
|
+
callerSignal.addEventListener("abort", onCallerAbort, {
|
|
479
|
+
once: true
|
|
480
|
+
});
|
|
481
|
+
controller.signal.addEventListener(
|
|
482
|
+
"abort",
|
|
483
|
+
() => {
|
|
484
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
485
|
+
},
|
|
486
|
+
{ once: true }
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
511
491
|
try {
|
|
512
|
-
await this.
|
|
513
|
-
|
|
492
|
+
const response = await this.fetch(url, {
|
|
493
|
+
method,
|
|
494
|
+
headers: requestHeaders,
|
|
495
|
+
body: processedBody,
|
|
496
|
+
...fetchOptions,
|
|
497
|
+
...controller ? { signal: controller.signal } : {}
|
|
498
|
+
});
|
|
499
|
+
if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
|
|
500
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
501
|
+
await response.body?.cancel();
|
|
502
|
+
lastError = new InsForgeError(
|
|
503
|
+
`Server error: ${response.status} ${response.statusText}`,
|
|
504
|
+
response.status,
|
|
505
|
+
"SERVER_ERROR"
|
|
506
|
+
);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (response.status === 204) {
|
|
510
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
511
|
+
return void 0;
|
|
512
|
+
}
|
|
513
|
+
let data;
|
|
514
|
+
const contentType = response.headers.get("content-type");
|
|
515
|
+
try {
|
|
516
|
+
if (contentType?.includes("json")) {
|
|
517
|
+
data = await response.json();
|
|
518
|
+
} else {
|
|
519
|
+
data = await response.text();
|
|
520
|
+
}
|
|
521
|
+
} catch (parseErr) {
|
|
522
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
523
|
+
throw new InsForgeError(
|
|
524
|
+
`Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
|
|
525
|
+
response.status,
|
|
526
|
+
response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
530
|
+
if (!response.ok) {
|
|
531
|
+
this.logger.logResponse(
|
|
532
|
+
method,
|
|
533
|
+
url,
|
|
534
|
+
response.status,
|
|
535
|
+
Date.now() - startTime,
|
|
536
|
+
data
|
|
537
|
+
);
|
|
538
|
+
if (data && typeof data === "object" && "error" in data) {
|
|
539
|
+
if (!data.statusCode && !data.status) {
|
|
540
|
+
data.statusCode = response.status;
|
|
541
|
+
}
|
|
542
|
+
const error = InsForgeError.fromApiError(data);
|
|
543
|
+
Object.keys(data).forEach((key) => {
|
|
544
|
+
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
545
|
+
error[key] = data[key];
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
throw new InsForgeError(
|
|
551
|
+
`Request failed: ${response.statusText}`,
|
|
552
|
+
response.status,
|
|
553
|
+
"REQUEST_FAILED"
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
this.logger.logResponse(
|
|
557
|
+
method,
|
|
558
|
+
url,
|
|
559
|
+
response.status,
|
|
560
|
+
Date.now() - startTime,
|
|
561
|
+
data
|
|
562
|
+
);
|
|
563
|
+
return data;
|
|
564
|
+
} catch (err) {
|
|
565
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
566
|
+
if (err?.name === "AbortError") {
|
|
567
|
+
if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
568
|
+
throw new InsForgeError(
|
|
569
|
+
`Request timed out after ${this.timeout}ms`,
|
|
570
|
+
408,
|
|
571
|
+
"REQUEST_TIMEOUT"
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
throw err;
|
|
575
|
+
}
|
|
576
|
+
if (err instanceof InsForgeError) {
|
|
577
|
+
throw err;
|
|
578
|
+
}
|
|
579
|
+
if (attempt < maxAttempts) {
|
|
580
|
+
lastError = err;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
throw new InsForgeError(
|
|
584
|
+
`Network request failed: ${err?.message || "Unknown error"}`,
|
|
585
|
+
0,
|
|
586
|
+
"NETWORK_ERROR"
|
|
587
|
+
);
|
|
514
588
|
}
|
|
515
|
-
this.tokenManager.clearSession();
|
|
516
|
-
this.http.setAuthToken(null);
|
|
517
|
-
clearCsrfToken();
|
|
518
|
-
return { error: null };
|
|
519
|
-
} catch (error) {
|
|
520
|
-
return {
|
|
521
|
-
error: new InsForgeError(
|
|
522
|
-
"Failed to sign out",
|
|
523
|
-
500,
|
|
524
|
-
"SIGNOUT_ERROR"
|
|
525
|
-
)
|
|
526
|
-
};
|
|
527
589
|
}
|
|
590
|
+
throw lastError || new InsForgeError(
|
|
591
|
+
"Request failed after all retry attempts",
|
|
592
|
+
0,
|
|
593
|
+
"NETWORK_ERROR"
|
|
594
|
+
);
|
|
528
595
|
}
|
|
529
|
-
|
|
530
|
-
* Get all public authentication configuration (OAuth + Email)
|
|
531
|
-
* Returns both OAuth providers and email authentication settings in one request
|
|
532
|
-
* This is a public endpoint that doesn't require authentication
|
|
533
|
-
*
|
|
534
|
-
* @returns Complete public authentication configuration including OAuth providers and email auth settings
|
|
535
|
-
*
|
|
536
|
-
* @example
|
|
537
|
-
* ```ts
|
|
538
|
-
* const { data, error } = await insforge.auth.getPublicAuthConfig();
|
|
539
|
-
* if (data) {
|
|
540
|
-
* console.log(`OAuth providers: ${data.oauth.data.length}`);
|
|
541
|
-
* console.log(`Password min length: ${data.email.passwordMinLength}`);
|
|
542
|
-
* }
|
|
543
|
-
* ```
|
|
544
|
-
*/
|
|
545
|
-
async getPublicAuthConfig() {
|
|
596
|
+
async request(method, path, options = {}) {
|
|
546
597
|
try {
|
|
547
|
-
|
|
548
|
-
return {
|
|
549
|
-
data: response,
|
|
550
|
-
error: null
|
|
551
|
-
};
|
|
598
|
+
return await this.handleRequest(method, path, { ...options });
|
|
552
599
|
} catch (error) {
|
|
553
|
-
if (error instanceof InsForgeError) {
|
|
554
|
-
|
|
600
|
+
if (error instanceof InsForgeError && error.statusCode === 401 && error.error === "INVALID_TOKEN" && this.autoRefreshToken) {
|
|
601
|
+
try {
|
|
602
|
+
const newTokenData = await this.handleTokenRefresh();
|
|
603
|
+
this.setAuthToken(newTokenData.accessToken);
|
|
604
|
+
this.tokenManager.saveSession(newTokenData);
|
|
605
|
+
if (newTokenData.csrfToken) {
|
|
606
|
+
setCsrfToken(newTokenData.csrfToken);
|
|
607
|
+
}
|
|
608
|
+
if (newTokenData.refreshToken) {
|
|
609
|
+
this.setRefreshToken(newTokenData.refreshToken);
|
|
610
|
+
}
|
|
611
|
+
return await this.handleRequest(method, path, { ...options });
|
|
612
|
+
} catch (error2) {
|
|
613
|
+
this.tokenManager.clearSession();
|
|
614
|
+
this.userToken = null;
|
|
615
|
+
this.refreshToken = null;
|
|
616
|
+
clearCsrfToken();
|
|
617
|
+
throw error2;
|
|
618
|
+
}
|
|
555
619
|
}
|
|
556
|
-
|
|
557
|
-
data: null,
|
|
558
|
-
error: new InsForgeError(
|
|
559
|
-
"An unexpected error occurred while fetching public authentication configuration",
|
|
560
|
-
500,
|
|
561
|
-
"UNEXPECTED_ERROR"
|
|
562
|
-
)
|
|
563
|
-
};
|
|
620
|
+
throw error;
|
|
564
621
|
}
|
|
565
622
|
}
|
|
566
|
-
/**
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
623
|
+
/** Performs a GET request. */
|
|
624
|
+
get(path, options) {
|
|
625
|
+
return this.request("GET", path, options);
|
|
626
|
+
}
|
|
627
|
+
/** Performs a POST request with an optional JSON body. */
|
|
628
|
+
post(path, body, options) {
|
|
629
|
+
return this.request("POST", path, { ...options, body });
|
|
630
|
+
}
|
|
631
|
+
/** Performs a PUT request with an optional JSON body. */
|
|
632
|
+
put(path, body, options) {
|
|
633
|
+
return this.request("PUT", path, { ...options, body });
|
|
634
|
+
}
|
|
635
|
+
/** Performs a PATCH request with an optional JSON body. */
|
|
636
|
+
patch(path, body, options) {
|
|
637
|
+
return this.request("PATCH", path, { ...options, body });
|
|
638
|
+
}
|
|
639
|
+
/** Performs a DELETE request. */
|
|
640
|
+
delete(path, options) {
|
|
641
|
+
return this.request("DELETE", path, options);
|
|
642
|
+
}
|
|
643
|
+
/** Sets or clears the user authentication token for subsequent requests. */
|
|
644
|
+
setAuthToken(token) {
|
|
645
|
+
this.userToken = token;
|
|
646
|
+
}
|
|
647
|
+
setRefreshToken(token) {
|
|
648
|
+
this.refreshToken = token;
|
|
649
|
+
}
|
|
650
|
+
/** Returns the current default headers including the authorization header if set. */
|
|
651
|
+
getHeaders() {
|
|
652
|
+
const headers = { ...this.defaultHeaders };
|
|
653
|
+
const authToken = this.userToken || this.anonKey;
|
|
654
|
+
if (authToken) {
|
|
655
|
+
headers["Authorization"] = `Bearer ${authToken}`;
|
|
656
|
+
}
|
|
657
|
+
return headers;
|
|
658
|
+
}
|
|
659
|
+
async handleTokenRefresh() {
|
|
660
|
+
if (this.isRefreshing) {
|
|
661
|
+
return this.refreshPromise;
|
|
604
662
|
}
|
|
663
|
+
this.isRefreshing = true;
|
|
664
|
+
this.refreshPromise = (async () => {
|
|
665
|
+
try {
|
|
666
|
+
const csrfToken = getCsrfToken();
|
|
667
|
+
const body = this.refreshToken ? { refreshToken: this.refreshToken } : void 0;
|
|
668
|
+
const response = await this.handleRequest(
|
|
669
|
+
"POST",
|
|
670
|
+
"/api/auth/sessions/current",
|
|
671
|
+
{
|
|
672
|
+
body,
|
|
673
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
674
|
+
credentials: "include"
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
return response;
|
|
678
|
+
} finally {
|
|
679
|
+
this.isRefreshing = false;
|
|
680
|
+
this.refreshPromise = null;
|
|
681
|
+
}
|
|
682
|
+
})();
|
|
683
|
+
return this.refreshPromise;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/modules/auth/helpers.ts
|
|
688
|
+
var PKCE_VERIFIER_KEY = "insforge_pkce_verifier";
|
|
689
|
+
function base64UrlEncode(buffer) {
|
|
690
|
+
const base64 = btoa(String.fromCharCode(...buffer));
|
|
691
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
692
|
+
}
|
|
693
|
+
function generateCodeVerifier() {
|
|
694
|
+
const array = new Uint8Array(32);
|
|
695
|
+
crypto.getRandomValues(array);
|
|
696
|
+
return base64UrlEncode(array);
|
|
697
|
+
}
|
|
698
|
+
async function generateCodeChallenge(verifier) {
|
|
699
|
+
const encoder = new TextEncoder();
|
|
700
|
+
const data = encoder.encode(verifier);
|
|
701
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
702
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
703
|
+
}
|
|
704
|
+
function storePkceVerifier(verifier) {
|
|
705
|
+
if (typeof sessionStorage !== "undefined") {
|
|
706
|
+
sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function retrievePkceVerifier() {
|
|
710
|
+
if (typeof sessionStorage === "undefined") {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
|
|
714
|
+
if (verifier) {
|
|
715
|
+
sessionStorage.removeItem(PKCE_VERIFIER_KEY);
|
|
716
|
+
}
|
|
717
|
+
return verifier;
|
|
718
|
+
}
|
|
719
|
+
function wrapError(error, fallbackMessage) {
|
|
720
|
+
if (error instanceof InsForgeError) {
|
|
721
|
+
return { data: null, error };
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
data: null,
|
|
725
|
+
error: new InsForgeError(
|
|
726
|
+
error instanceof Error ? error.message : fallbackMessage,
|
|
727
|
+
500,
|
|
728
|
+
"UNEXPECTED_ERROR"
|
|
729
|
+
)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function cleanUrlParams(...params) {
|
|
733
|
+
if (typeof window === "undefined") {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const url = new URL(window.location.href);
|
|
737
|
+
params.forEach((p) => url.searchParams.delete(p));
|
|
738
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/modules/auth/auth.ts
|
|
742
|
+
var import_shared_schemas = require("@insforge/shared-schemas");
|
|
743
|
+
var Auth = class {
|
|
744
|
+
constructor(http, tokenManager, options = {}) {
|
|
745
|
+
this.http = http;
|
|
746
|
+
this.tokenManager = tokenManager;
|
|
747
|
+
this.options = options;
|
|
748
|
+
this.authCallbackHandled = this.detectAuthCallback();
|
|
749
|
+
}
|
|
750
|
+
isServerMode() {
|
|
751
|
+
return !!this.options.isServerMode;
|
|
605
752
|
}
|
|
606
753
|
/**
|
|
607
|
-
*
|
|
608
|
-
*
|
|
754
|
+
* Save session from API response
|
|
755
|
+
* Handles token storage, CSRF token, and HTTP auth header
|
|
609
756
|
*/
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
error: new InsForgeError(
|
|
624
|
-
"An unexpected error occurred while fetching user profile",
|
|
625
|
-
500,
|
|
626
|
-
"UNEXPECTED_ERROR"
|
|
627
|
-
)
|
|
628
|
-
};
|
|
757
|
+
saveSessionFromResponse(response) {
|
|
758
|
+
if (!response.accessToken || !response.user) {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
const session = {
|
|
762
|
+
accessToken: response.accessToken,
|
|
763
|
+
user: response.user
|
|
764
|
+
};
|
|
765
|
+
if (!this.isServerMode() && response.csrfToken) {
|
|
766
|
+
setCsrfToken(response.csrfToken);
|
|
767
|
+
}
|
|
768
|
+
if (!this.isServerMode()) {
|
|
769
|
+
this.tokenManager.saveSession(session);
|
|
629
770
|
}
|
|
771
|
+
this.http.setAuthToken(response.accessToken);
|
|
772
|
+
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
773
|
+
return true;
|
|
630
774
|
}
|
|
775
|
+
// ============================================================================
|
|
776
|
+
// OAuth Callback Detection (runs on initialization)
|
|
777
|
+
// ============================================================================
|
|
631
778
|
/**
|
|
632
|
-
*
|
|
633
|
-
*
|
|
779
|
+
* Detect and handle OAuth callback parameters in URL
|
|
780
|
+
* Supports PKCE flow (insforge_code)
|
|
634
781
|
*/
|
|
635
|
-
async
|
|
782
|
+
async detectAuthCallback() {
|
|
783
|
+
if (this.isServerMode() || typeof window === "undefined") return;
|
|
636
784
|
try {
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
785
|
+
const params = new URLSearchParams(window.location.search);
|
|
786
|
+
const error = params.get("error");
|
|
787
|
+
if (error) {
|
|
788
|
+
cleanUrlParams("error");
|
|
789
|
+
console.debug("OAuth callback error:", error);
|
|
790
|
+
return;
|
|
641
791
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
{
|
|
649
|
-
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
650
|
-
credentials: "include"
|
|
651
|
-
}
|
|
652
|
-
);
|
|
653
|
-
if (response.accessToken) {
|
|
654
|
-
this.tokenManager.setMemoryMode();
|
|
655
|
-
this.tokenManager.setAccessToken(response.accessToken);
|
|
656
|
-
this.http.setAuthToken(response.accessToken);
|
|
657
|
-
if (response.user) {
|
|
658
|
-
this.tokenManager.setUser(response.user);
|
|
659
|
-
}
|
|
660
|
-
if (response.csrfToken) {
|
|
661
|
-
setCsrfToken(response.csrfToken);
|
|
662
|
-
}
|
|
663
|
-
return {
|
|
664
|
-
data: { session: this.tokenManager.getSession() },
|
|
665
|
-
error: null
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
} catch (error) {
|
|
669
|
-
if (error instanceof InsForgeError) {
|
|
670
|
-
if (error.statusCode === 404) {
|
|
671
|
-
this.tokenManager.setStorageMode();
|
|
672
|
-
const session2 = this.tokenManager.getSession();
|
|
673
|
-
if (session2) {
|
|
674
|
-
return { data: { session: session2 }, error: null };
|
|
675
|
-
}
|
|
676
|
-
return { data: { session: null }, error: null };
|
|
677
|
-
}
|
|
678
|
-
return { data: { session: null }, error };
|
|
679
|
-
}
|
|
792
|
+
const code = params.get("insforge_code");
|
|
793
|
+
if (code) {
|
|
794
|
+
cleanUrlParams("insforge_code");
|
|
795
|
+
const { error: exchangeError } = await this.exchangeOAuthCode(code);
|
|
796
|
+
if (exchangeError) {
|
|
797
|
+
console.debug("OAuth code exchange failed:", exchangeError.message);
|
|
680
798
|
}
|
|
799
|
+
return;
|
|
681
800
|
}
|
|
682
|
-
return { data: { session: null }, error: null };
|
|
683
801
|
} catch (error) {
|
|
684
|
-
|
|
685
|
-
return { data: { session: null }, error };
|
|
686
|
-
}
|
|
687
|
-
return {
|
|
688
|
-
data: { session: null },
|
|
689
|
-
error: new InsForgeError(
|
|
690
|
-
"An unexpected error occurred while getting session",
|
|
691
|
-
500,
|
|
692
|
-
"UNEXPECTED_ERROR"
|
|
693
|
-
)
|
|
694
|
-
};
|
|
802
|
+
console.debug("OAuth callback detection skipped:", error);
|
|
695
803
|
}
|
|
696
804
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
*/
|
|
702
|
-
async setProfile(profile) {
|
|
805
|
+
// ============================================================================
|
|
806
|
+
// Sign Up / Sign In / Sign Out
|
|
807
|
+
// ============================================================================
|
|
808
|
+
async signUp(request) {
|
|
703
809
|
try {
|
|
704
|
-
const response = await this.http.
|
|
705
|
-
"/api/auth/
|
|
706
|
-
|
|
810
|
+
const response = await this.http.post(
|
|
811
|
+
this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
|
|
812
|
+
request,
|
|
813
|
+
{ credentials: "include" }
|
|
707
814
|
);
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
error: null
|
|
711
|
-
};
|
|
712
|
-
} catch (error) {
|
|
713
|
-
if (error instanceof InsForgeError) {
|
|
714
|
-
return { data: null, error };
|
|
815
|
+
if (response.accessToken && response.user) {
|
|
816
|
+
this.saveSessionFromResponse(response);
|
|
715
817
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
)
|
|
723
|
-
};
|
|
818
|
+
if (response.refreshToken) {
|
|
819
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
820
|
+
}
|
|
821
|
+
return { data: response, error: null };
|
|
822
|
+
} catch (error) {
|
|
823
|
+
return wrapError(error, "An unexpected error occurred during sign up");
|
|
724
824
|
}
|
|
725
825
|
}
|
|
726
|
-
|
|
727
|
-
* Send email verification (code or link based on config)
|
|
728
|
-
*
|
|
729
|
-
* Send email verification using the method configured in auth settings (verifyEmailMethod).
|
|
730
|
-
* When method is 'code', sends a 6-digit numeric code. When method is 'link', sends a magic link.
|
|
731
|
-
* Prevents user enumeration by returning success even if email doesn't exist.
|
|
732
|
-
*/
|
|
733
|
-
async sendVerificationEmail(request) {
|
|
826
|
+
async signInWithPassword(request) {
|
|
734
827
|
try {
|
|
735
828
|
const response = await this.http.post(
|
|
736
|
-
"/api/auth/
|
|
737
|
-
request
|
|
829
|
+
this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
|
|
830
|
+
request,
|
|
831
|
+
{ credentials: "include" }
|
|
738
832
|
);
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
833
|
+
this.saveSessionFromResponse(response);
|
|
834
|
+
if (response.refreshToken) {
|
|
835
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
836
|
+
}
|
|
837
|
+
return { data: response, error: null };
|
|
743
838
|
} catch (error) {
|
|
744
|
-
|
|
745
|
-
|
|
839
|
+
return wrapError(error, "An unexpected error occurred during sign in");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async signOut() {
|
|
843
|
+
try {
|
|
844
|
+
try {
|
|
845
|
+
await this.http.post(
|
|
846
|
+
this.isServerMode() ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
847
|
+
void 0,
|
|
848
|
+
{ credentials: "include" }
|
|
849
|
+
);
|
|
850
|
+
} catch {
|
|
746
851
|
}
|
|
852
|
+
this.tokenManager.clearSession();
|
|
853
|
+
this.http.setAuthToken(null);
|
|
854
|
+
this.http.setRefreshToken(null);
|
|
855
|
+
if (!this.isServerMode()) {
|
|
856
|
+
clearCsrfToken();
|
|
857
|
+
}
|
|
858
|
+
return { error: null };
|
|
859
|
+
} catch {
|
|
747
860
|
return {
|
|
748
|
-
|
|
749
|
-
error: new InsForgeError(
|
|
750
|
-
"An unexpected error occurred while sending verification code",
|
|
751
|
-
500,
|
|
752
|
-
"UNEXPECTED_ERROR"
|
|
753
|
-
)
|
|
861
|
+
error: new InsForgeError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
754
862
|
};
|
|
755
863
|
}
|
|
756
864
|
}
|
|
865
|
+
// ============================================================================
|
|
866
|
+
// OAuth Authentication
|
|
867
|
+
// ============================================================================
|
|
757
868
|
/**
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
* Send password reset email using the method configured in auth settings (resetPasswordMethod).
|
|
761
|
-
* When method is 'code', sends a 6-digit numeric code for two-step flow.
|
|
762
|
-
* When method is 'link', sends a magic link.
|
|
763
|
-
* Prevents user enumeration by returning success even if email doesn't exist.
|
|
869
|
+
* Sign in with OAuth provider using PKCE flow
|
|
764
870
|
*/
|
|
765
|
-
async
|
|
871
|
+
async signInWithOAuth(options) {
|
|
766
872
|
try {
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
873
|
+
const { provider, redirectTo, skipBrowserRedirect } = options;
|
|
874
|
+
const providerKey = encodeURIComponent(provider.toLowerCase());
|
|
875
|
+
const codeVerifier = generateCodeVerifier();
|
|
876
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
877
|
+
storePkceVerifier(codeVerifier);
|
|
878
|
+
const params = { code_challenge: codeChallenge };
|
|
879
|
+
if (redirectTo) params.redirect_uri = redirectTo;
|
|
880
|
+
const isBuiltInProvider = import_shared_schemas.oAuthProvidersSchema.options.includes(
|
|
881
|
+
providerKey
|
|
770
882
|
);
|
|
883
|
+
const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
|
|
884
|
+
const response = await this.http.get(oauthPath, {
|
|
885
|
+
params
|
|
886
|
+
});
|
|
887
|
+
if (!this.isServerMode() && typeof window !== "undefined" && !skipBrowserRedirect) {
|
|
888
|
+
window.location.href = response.authUrl;
|
|
889
|
+
return { data: {}, error: null };
|
|
890
|
+
}
|
|
771
891
|
return {
|
|
772
|
-
data: response,
|
|
892
|
+
data: { url: response.authUrl, provider: providerKey, codeVerifier },
|
|
773
893
|
error: null
|
|
774
894
|
};
|
|
775
895
|
} catch (error) {
|
|
776
896
|
if (error instanceof InsForgeError) {
|
|
777
|
-
return { data:
|
|
897
|
+
return { data: {}, error };
|
|
778
898
|
}
|
|
779
899
|
return {
|
|
780
|
-
data:
|
|
900
|
+
data: {},
|
|
781
901
|
error: new InsForgeError(
|
|
782
|
-
"An unexpected error occurred
|
|
902
|
+
"An unexpected error occurred during OAuth initialization",
|
|
783
903
|
500,
|
|
784
904
|
"UNEXPECTED_ERROR"
|
|
785
905
|
)
|
|
@@ -787,123 +907,292 @@ var Auth = class {
|
|
|
787
907
|
}
|
|
788
908
|
}
|
|
789
909
|
/**
|
|
790
|
-
* Exchange
|
|
791
|
-
*
|
|
792
|
-
* Step 1 of two-step password reset flow (only used when resetPasswordMethod is 'code'):
|
|
793
|
-
* 1. Verify the 6-digit code sent to user's email
|
|
794
|
-
* 2. Return a reset token that can be used to actually reset the password
|
|
795
|
-
*
|
|
796
|
-
* This endpoint is not used when resetPasswordMethod is 'link' (magic link flow is direct).
|
|
910
|
+
* Exchange OAuth authorization code for tokens (PKCE flow)
|
|
911
|
+
* Called automatically on initialization when insforge_code is in URL
|
|
797
912
|
*/
|
|
798
|
-
async
|
|
913
|
+
async exchangeOAuthCode(code, codeVerifier) {
|
|
799
914
|
try {
|
|
915
|
+
const verifier = codeVerifier ?? retrievePkceVerifier();
|
|
916
|
+
if (!verifier) {
|
|
917
|
+
return {
|
|
918
|
+
data: null,
|
|
919
|
+
error: new InsForgeError(
|
|
920
|
+
"PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
|
|
921
|
+
400,
|
|
922
|
+
"PKCE_VERIFIER_MISSING"
|
|
923
|
+
)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
const request = {
|
|
927
|
+
code,
|
|
928
|
+
code_verifier: verifier
|
|
929
|
+
};
|
|
800
930
|
const response = await this.http.post(
|
|
801
|
-
"/api/auth/
|
|
802
|
-
request
|
|
931
|
+
this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
|
|
932
|
+
request,
|
|
933
|
+
{ credentials: "include" }
|
|
803
934
|
);
|
|
935
|
+
this.saveSessionFromResponse(response);
|
|
804
936
|
return {
|
|
805
937
|
data: response,
|
|
806
938
|
error: null
|
|
807
939
|
};
|
|
808
940
|
} catch (error) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
data: null,
|
|
814
|
-
error: new InsForgeError(
|
|
815
|
-
"An unexpected error occurred while verifying reset code",
|
|
816
|
-
500,
|
|
817
|
-
"UNEXPECTED_ERROR"
|
|
818
|
-
)
|
|
819
|
-
};
|
|
941
|
+
return wrapError(
|
|
942
|
+
error,
|
|
943
|
+
"An unexpected error occurred during OAuth code exchange"
|
|
944
|
+
);
|
|
820
945
|
}
|
|
821
946
|
}
|
|
822
947
|
/**
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
* Reset user password with a token. The token can be:
|
|
826
|
-
* - Magic link token (64-character hex token from send-reset-password when method is 'link')
|
|
827
|
-
* - Reset token (from exchange-reset-password-token after code verification when method is 'code')
|
|
828
|
-
*
|
|
829
|
-
* Both token types use RESET_PASSWORD purpose and are verified the same way.
|
|
948
|
+
* Sign in with an ID token from a native SDK (Google One Tap, etc.)
|
|
949
|
+
* Use this for native mobile apps or Google One Tap on web.
|
|
830
950
|
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
* - Link method: send-reset-password → reset-password (with link token directly)
|
|
951
|
+
* @param credentials.provider - The identity provider (currently only 'google' is supported)
|
|
952
|
+
* @param credentials.token - The ID token from the native SDK
|
|
834
953
|
*/
|
|
835
|
-
async
|
|
954
|
+
async signInWithIdToken(credentials) {
|
|
836
955
|
try {
|
|
956
|
+
const { provider, token } = credentials;
|
|
837
957
|
const response = await this.http.post(
|
|
838
|
-
"/api/auth/
|
|
839
|
-
|
|
958
|
+
"/api/auth/id-token?client_type=mobile",
|
|
959
|
+
{ provider, token },
|
|
960
|
+
{ credentials: "include" }
|
|
840
961
|
);
|
|
962
|
+
this.saveSessionFromResponse(response);
|
|
963
|
+
if (response.refreshToken) {
|
|
964
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
965
|
+
}
|
|
841
966
|
return {
|
|
842
967
|
data: response,
|
|
843
968
|
error: null
|
|
844
969
|
};
|
|
845
970
|
} catch (error) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
data: null,
|
|
851
|
-
error: new InsForgeError(
|
|
852
|
-
"An unexpected error occurred while resetting password",
|
|
853
|
-
500,
|
|
854
|
-
"UNEXPECTED_ERROR"
|
|
855
|
-
)
|
|
856
|
-
};
|
|
971
|
+
return wrapError(
|
|
972
|
+
error,
|
|
973
|
+
"An unexpected error occurred during ID token sign in"
|
|
974
|
+
);
|
|
857
975
|
}
|
|
858
976
|
}
|
|
977
|
+
// ============================================================================
|
|
978
|
+
// Session Management
|
|
979
|
+
// ============================================================================
|
|
859
980
|
/**
|
|
860
|
-
*
|
|
981
|
+
* Refresh the current auth session.
|
|
861
982
|
*
|
|
862
|
-
*
|
|
863
|
-
* -
|
|
864
|
-
* - Link verification: Provide only `otp` (64-character hex token from magic link)
|
|
983
|
+
* Browser mode:
|
|
984
|
+
* - Uses httpOnly refresh cookie and optional CSRF header.
|
|
865
985
|
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* The email verification link sent to users always points to the backend API endpoint.
|
|
869
|
-
* If `verifyEmailRedirectTo` is configured, the backend will redirect to that URL after successful verification.
|
|
870
|
-
* Otherwise, a default success page is displayed.
|
|
986
|
+
* Server mode (`isServerMode: true`):
|
|
987
|
+
* - Uses mobile auth flow and requires `refreshToken` in request body.
|
|
871
988
|
*/
|
|
872
|
-
async
|
|
989
|
+
async refreshSession(options) {
|
|
873
990
|
try {
|
|
991
|
+
if (this.isServerMode() && !options?.refreshToken) {
|
|
992
|
+
return {
|
|
993
|
+
data: null,
|
|
994
|
+
error: new InsForgeError(
|
|
995
|
+
"refreshToken is required when refreshing session in server mode",
|
|
996
|
+
400,
|
|
997
|
+
"REFRESH_TOKEN_REQUIRED"
|
|
998
|
+
)
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
|
|
874
1002
|
const response = await this.http.post(
|
|
875
|
-
"/api/auth/
|
|
876
|
-
|
|
1003
|
+
this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
|
|
1004
|
+
this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
|
|
1005
|
+
{
|
|
1006
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
1007
|
+
credentials: "include"
|
|
1008
|
+
}
|
|
877
1009
|
);
|
|
878
|
-
if (
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1010
|
+
if (response.accessToken) {
|
|
1011
|
+
this.saveSessionFromResponse(response);
|
|
1012
|
+
}
|
|
1013
|
+
return { data: response, error: null };
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
return wrapError(
|
|
1016
|
+
error,
|
|
1017
|
+
"An unexpected error occurred during session refresh"
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Get current user, automatically waits for pending OAuth callback
|
|
1023
|
+
*/
|
|
1024
|
+
async getCurrentUser() {
|
|
1025
|
+
await this.authCallbackHandled;
|
|
1026
|
+
try {
|
|
1027
|
+
if (this.isServerMode()) {
|
|
1028
|
+
const accessToken = this.tokenManager.getAccessToken();
|
|
1029
|
+
if (!accessToken) return { data: { user: null }, error: null };
|
|
1030
|
+
this.http.setAuthToken(accessToken);
|
|
1031
|
+
const response = await this.http.get(
|
|
1032
|
+
"/api/auth/sessions/current"
|
|
1033
|
+
);
|
|
1034
|
+
const user = response.user ?? null;
|
|
1035
|
+
return { data: { user }, error: null };
|
|
1036
|
+
}
|
|
1037
|
+
const session = this.tokenManager.getSession();
|
|
1038
|
+
if (session) {
|
|
1039
|
+
this.http.setAuthToken(session.accessToken);
|
|
1040
|
+
return { data: { user: session.user }, error: null };
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof window !== "undefined") {
|
|
1043
|
+
const { data: refreshed, error: refreshError } = await this.refreshSession();
|
|
1044
|
+
if (refreshError) {
|
|
1045
|
+
return { data: { user: null }, error: refreshError };
|
|
1046
|
+
}
|
|
1047
|
+
if (refreshed?.accessToken) {
|
|
1048
|
+
return { data: { user: refreshed.user ?? null }, error: null };
|
|
887
1049
|
}
|
|
888
1050
|
}
|
|
889
|
-
return {
|
|
890
|
-
data: response,
|
|
891
|
-
error: null
|
|
892
|
-
};
|
|
1051
|
+
return { data: { user: null }, error: null };
|
|
893
1052
|
} catch (error) {
|
|
894
1053
|
if (error instanceof InsForgeError) {
|
|
895
|
-
return { data: null, error };
|
|
1054
|
+
return { data: { user: null }, error };
|
|
896
1055
|
}
|
|
897
1056
|
return {
|
|
898
|
-
data: null,
|
|
1057
|
+
data: { user: null },
|
|
899
1058
|
error: new InsForgeError(
|
|
900
|
-
"An unexpected error occurred while
|
|
1059
|
+
"An unexpected error occurred while getting user",
|
|
901
1060
|
500,
|
|
902
1061
|
"UNEXPECTED_ERROR"
|
|
903
1062
|
)
|
|
904
1063
|
};
|
|
905
1064
|
}
|
|
906
1065
|
}
|
|
1066
|
+
// ============================================================================
|
|
1067
|
+
// Profile Management
|
|
1068
|
+
// ============================================================================
|
|
1069
|
+
async getProfile(userId) {
|
|
1070
|
+
try {
|
|
1071
|
+
const response = await this.http.get(
|
|
1072
|
+
`/api/auth/profiles/${userId}`
|
|
1073
|
+
);
|
|
1074
|
+
return { data: response, error: null };
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
return wrapError(
|
|
1077
|
+
error,
|
|
1078
|
+
"An unexpected error occurred while fetching user profile"
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async setProfile(profile) {
|
|
1083
|
+
try {
|
|
1084
|
+
const response = await this.http.patch(
|
|
1085
|
+
"/api/auth/profiles/current",
|
|
1086
|
+
{
|
|
1087
|
+
profile
|
|
1088
|
+
}
|
|
1089
|
+
);
|
|
1090
|
+
const currentUser = this.tokenManager.getUser();
|
|
1091
|
+
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
1092
|
+
this.tokenManager.setUser({
|
|
1093
|
+
...currentUser,
|
|
1094
|
+
profile: response.profile
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
return { data: response, error: null };
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
return wrapError(
|
|
1100
|
+
error,
|
|
1101
|
+
"An unexpected error occurred while updating user profile"
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
// ============================================================================
|
|
1106
|
+
// Email Verification
|
|
1107
|
+
// ============================================================================
|
|
1108
|
+
async resendVerificationEmail(request) {
|
|
1109
|
+
try {
|
|
1110
|
+
const response = await this.http.post("/api/auth/email/send-verification", request);
|
|
1111
|
+
return { data: response, error: null };
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
return wrapError(
|
|
1114
|
+
error,
|
|
1115
|
+
"An unexpected error occurred while sending verification email"
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
async verifyEmail(request) {
|
|
1120
|
+
try {
|
|
1121
|
+
const response = await this.http.post(
|
|
1122
|
+
this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
|
|
1123
|
+
request,
|
|
1124
|
+
{ credentials: "include" }
|
|
1125
|
+
);
|
|
1126
|
+
this.saveSessionFromResponse(response);
|
|
1127
|
+
if (response.refreshToken) {
|
|
1128
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1129
|
+
}
|
|
1130
|
+
return { data: response, error: null };
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
return wrapError(
|
|
1133
|
+
error,
|
|
1134
|
+
"An unexpected error occurred while verifying email"
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
// ============================================================================
|
|
1139
|
+
// Password Reset
|
|
1140
|
+
// ============================================================================
|
|
1141
|
+
async sendResetPasswordEmail(request) {
|
|
1142
|
+
try {
|
|
1143
|
+
const response = await this.http.post("/api/auth/email/send-reset-password", request);
|
|
1144
|
+
return { data: response, error: null };
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
return wrapError(
|
|
1147
|
+
error,
|
|
1148
|
+
"An unexpected error occurred while sending password reset email"
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
async exchangeResetPasswordToken(request) {
|
|
1153
|
+
try {
|
|
1154
|
+
const response = await this.http.post(
|
|
1155
|
+
"/api/auth/email/exchange-reset-password-token",
|
|
1156
|
+
request
|
|
1157
|
+
);
|
|
1158
|
+
return { data: response, error: null };
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
return wrapError(
|
|
1161
|
+
error,
|
|
1162
|
+
"An unexpected error occurred while verifying reset code"
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async resetPassword(request) {
|
|
1167
|
+
try {
|
|
1168
|
+
const response = await this.http.post(
|
|
1169
|
+
"/api/auth/email/reset-password",
|
|
1170
|
+
request
|
|
1171
|
+
);
|
|
1172
|
+
return { data: response, error: null };
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
return wrapError(
|
|
1175
|
+
error,
|
|
1176
|
+
"An unexpected error occurred while resetting password"
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
// ============================================================================
|
|
1181
|
+
// Configuration
|
|
1182
|
+
// ============================================================================
|
|
1183
|
+
async getPublicAuthConfig() {
|
|
1184
|
+
try {
|
|
1185
|
+
const response = await this.http.get(
|
|
1186
|
+
"/api/auth/public-config"
|
|
1187
|
+
);
|
|
1188
|
+
return { data: response, error: null };
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
return wrapError(
|
|
1191
|
+
error,
|
|
1192
|
+
"An unexpected error occurred while fetching auth configuration"
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
907
1196
|
};
|
|
908
1197
|
|
|
909
1198
|
// src/modules/database-postgrest.ts
|
|
@@ -912,8 +1201,10 @@ function createInsForgePostgrestFetch(httpClient, tokenManager) {
|
|
|
912
1201
|
return async (input, init) => {
|
|
913
1202
|
const url = typeof input === "string" ? input : input.toString();
|
|
914
1203
|
const urlObj = new URL(url);
|
|
915
|
-
const
|
|
916
|
-
const
|
|
1204
|
+
const pathname = urlObj.pathname.slice(1);
|
|
1205
|
+
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
1206
|
+
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
1207
|
+
const insforgeUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
917
1208
|
const token = tokenManager.getAccessToken();
|
|
918
1209
|
const httpHeaders = httpClient.getHeaders();
|
|
919
1210
|
const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
|
|
@@ -973,6 +1264,25 @@ var Database = class {
|
|
|
973
1264
|
from(table) {
|
|
974
1265
|
return this.postgrest.from(table);
|
|
975
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Call a PostgreSQL function (RPC)
|
|
1269
|
+
*
|
|
1270
|
+
* @example
|
|
1271
|
+
* // Call a function with parameters
|
|
1272
|
+
* const { data, error } = await client.database
|
|
1273
|
+
* .rpc('get_user_stats', { user_id: 123 });
|
|
1274
|
+
*
|
|
1275
|
+
* // Call a function with no parameters
|
|
1276
|
+
* const { data, error } = await client.database
|
|
1277
|
+
* .rpc('get_all_active_users');
|
|
1278
|
+
*
|
|
1279
|
+
* // With options (head, count, get)
|
|
1280
|
+
* const { data, count } = await client.database
|
|
1281
|
+
* .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
|
|
1282
|
+
*/
|
|
1283
|
+
rpc(fn, args, options) {
|
|
1284
|
+
return this.postgrest.rpc(fn, args, options);
|
|
1285
|
+
}
|
|
976
1286
|
};
|
|
977
1287
|
|
|
978
1288
|
// src/modules/storage.ts
|
|
@@ -1257,6 +1567,7 @@ var AI = class {
|
|
|
1257
1567
|
this.http = http;
|
|
1258
1568
|
this.chat = new Chat(http);
|
|
1259
1569
|
this.images = new Images(http);
|
|
1570
|
+
this.embeddings = new Embeddings(http);
|
|
1260
1571
|
}
|
|
1261
1572
|
};
|
|
1262
1573
|
var Chat = class {
|
|
@@ -1280,16 +1591,46 @@ var ChatCompletions = class {
|
|
|
1280
1591
|
* });
|
|
1281
1592
|
* console.log(completion.choices[0].message.content);
|
|
1282
1593
|
*
|
|
1283
|
-
* // With images
|
|
1594
|
+
* // With images (OpenAI-compatible format)
|
|
1284
1595
|
* const response = await client.ai.chat.completions.create({
|
|
1285
1596
|
* model: 'gpt-4-vision',
|
|
1286
1597
|
* messages: [{
|
|
1287
1598
|
* role: 'user',
|
|
1288
|
-
* content:
|
|
1289
|
-
*
|
|
1599
|
+
* content: [
|
|
1600
|
+
* { type: 'text', text: 'What is in this image?' },
|
|
1601
|
+
* { type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
|
|
1602
|
+
* ]
|
|
1290
1603
|
* }]
|
|
1291
1604
|
* });
|
|
1292
1605
|
*
|
|
1606
|
+
* // With PDF files
|
|
1607
|
+
* const pdfResponse = await client.ai.chat.completions.create({
|
|
1608
|
+
* model: 'anthropic/claude-3.5-sonnet',
|
|
1609
|
+
* messages: [{
|
|
1610
|
+
* role: 'user',
|
|
1611
|
+
* content: [
|
|
1612
|
+
* { type: 'text', text: 'Summarize this document' },
|
|
1613
|
+
* { type: 'file', file: { filename: 'doc.pdf', file_data: 'https://example.com/doc.pdf' } }
|
|
1614
|
+
* ]
|
|
1615
|
+
* }],
|
|
1616
|
+
* fileParser: { enabled: true, pdf: { engine: 'mistral-ocr' } }
|
|
1617
|
+
* });
|
|
1618
|
+
*
|
|
1619
|
+
* // With web search
|
|
1620
|
+
* const searchResponse = await client.ai.chat.completions.create({
|
|
1621
|
+
* model: 'openai/gpt-4',
|
|
1622
|
+
* messages: [{ role: 'user', content: 'What are the latest news about AI?' }],
|
|
1623
|
+
* webSearch: { enabled: true, maxResults: 5 }
|
|
1624
|
+
* });
|
|
1625
|
+
* // Access citations from response.choices[0].message.annotations
|
|
1626
|
+
*
|
|
1627
|
+
* // With thinking/reasoning mode (Anthropic models)
|
|
1628
|
+
* const thinkingResponse = await client.ai.chat.completions.create({
|
|
1629
|
+
* model: 'anthropic/claude-3.5-sonnet',
|
|
1630
|
+
* messages: [{ role: 'user', content: 'Solve this complex math problem...' }],
|
|
1631
|
+
* thinking: true
|
|
1632
|
+
* });
|
|
1633
|
+
*
|
|
1293
1634
|
* // Streaming - returns async iterable
|
|
1294
1635
|
* const stream = await client.ai.chat.completions.create({
|
|
1295
1636
|
* model: 'gpt-4',
|
|
@@ -1311,7 +1652,15 @@ var ChatCompletions = class {
|
|
|
1311
1652
|
temperature: params.temperature,
|
|
1312
1653
|
maxTokens: params.maxTokens,
|
|
1313
1654
|
topP: params.topP,
|
|
1314
|
-
stream: params.stream
|
|
1655
|
+
stream: params.stream,
|
|
1656
|
+
// New plugin options
|
|
1657
|
+
webSearch: params.webSearch,
|
|
1658
|
+
fileParser: params.fileParser,
|
|
1659
|
+
thinking: params.thinking,
|
|
1660
|
+
// Tool calling options
|
|
1661
|
+
tools: params.tools,
|
|
1662
|
+
toolChoice: params.toolChoice,
|
|
1663
|
+
parallelToolCalls: params.parallelToolCalls
|
|
1315
1664
|
};
|
|
1316
1665
|
if (params.stream) {
|
|
1317
1666
|
const headers = this.http.getHeaders();
|
|
@@ -1345,9 +1694,13 @@ var ChatCompletions = class {
|
|
|
1345
1694
|
index: 0,
|
|
1346
1695
|
message: {
|
|
1347
1696
|
role: "assistant",
|
|
1348
|
-
content
|
|
1697
|
+
content,
|
|
1698
|
+
// Include tool_calls if present (from tool calling)
|
|
1699
|
+
...response.tool_calls?.length && { tool_calls: response.tool_calls },
|
|
1700
|
+
// Include annotations if present (from web search or file parsing)
|
|
1701
|
+
...response.annotations?.length && { annotations: response.annotations }
|
|
1349
1702
|
},
|
|
1350
|
-
finish_reason: "stop"
|
|
1703
|
+
finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
|
|
1351
1704
|
}
|
|
1352
1705
|
],
|
|
1353
1706
|
usage: response.metadata?.usage || {
|
|
@@ -1389,7 +1742,24 @@ var ChatCompletions = class {
|
|
|
1389
1742
|
delta: {
|
|
1390
1743
|
content: data.chunk || data.content
|
|
1391
1744
|
},
|
|
1392
|
-
finish_reason:
|
|
1745
|
+
finish_reason: null
|
|
1746
|
+
}
|
|
1747
|
+
]
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
if (data.tool_calls?.length) {
|
|
1751
|
+
yield {
|
|
1752
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1753
|
+
object: "chat.completion.chunk",
|
|
1754
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1755
|
+
model,
|
|
1756
|
+
choices: [
|
|
1757
|
+
{
|
|
1758
|
+
index: 0,
|
|
1759
|
+
delta: {
|
|
1760
|
+
tool_calls: data.tool_calls
|
|
1761
|
+
},
|
|
1762
|
+
finish_reason: "tool_calls"
|
|
1393
1763
|
}
|
|
1394
1764
|
]
|
|
1395
1765
|
};
|
|
@@ -1410,6 +1780,65 @@ var ChatCompletions = class {
|
|
|
1410
1780
|
}
|
|
1411
1781
|
}
|
|
1412
1782
|
};
|
|
1783
|
+
var Embeddings = class {
|
|
1784
|
+
constructor(http) {
|
|
1785
|
+
this.http = http;
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Create embeddings for text input - OpenAI-like response format
|
|
1789
|
+
*
|
|
1790
|
+
* @example
|
|
1791
|
+
* ```typescript
|
|
1792
|
+
* // Single text input
|
|
1793
|
+
* const response = await client.ai.embeddings.create({
|
|
1794
|
+
* model: 'openai/text-embedding-3-small',
|
|
1795
|
+
* input: 'Hello world'
|
|
1796
|
+
* });
|
|
1797
|
+
* console.log(response.data[0].embedding); // number[]
|
|
1798
|
+
*
|
|
1799
|
+
* // Multiple text inputs
|
|
1800
|
+
* const response = await client.ai.embeddings.create({
|
|
1801
|
+
* model: 'openai/text-embedding-3-small',
|
|
1802
|
+
* input: ['Hello world', 'Goodbye world']
|
|
1803
|
+
* });
|
|
1804
|
+
* response.data.forEach((item, i) => {
|
|
1805
|
+
* console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
|
|
1806
|
+
* });
|
|
1807
|
+
*
|
|
1808
|
+
* // With custom dimensions (if supported by model)
|
|
1809
|
+
* const response = await client.ai.embeddings.create({
|
|
1810
|
+
* model: 'openai/text-embedding-3-small',
|
|
1811
|
+
* input: 'Hello world',
|
|
1812
|
+
* dimensions: 256
|
|
1813
|
+
* });
|
|
1814
|
+
*
|
|
1815
|
+
* // With base64 encoding format
|
|
1816
|
+
* const response = await client.ai.embeddings.create({
|
|
1817
|
+
* model: 'openai/text-embedding-3-small',
|
|
1818
|
+
* input: 'Hello world',
|
|
1819
|
+
* encoding_format: 'base64'
|
|
1820
|
+
* });
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
async create(params) {
|
|
1824
|
+
const response = await this.http.post(
|
|
1825
|
+
"/api/ai/embeddings",
|
|
1826
|
+
params
|
|
1827
|
+
);
|
|
1828
|
+
return {
|
|
1829
|
+
object: response.object,
|
|
1830
|
+
data: response.data,
|
|
1831
|
+
model: response.metadata?.model,
|
|
1832
|
+
usage: response.metadata?.usage ? {
|
|
1833
|
+
prompt_tokens: response.metadata.usage.promptTokens || 0,
|
|
1834
|
+
total_tokens: response.metadata.usage.totalTokens || 0
|
|
1835
|
+
} : {
|
|
1836
|
+
prompt_tokens: 0,
|
|
1837
|
+
total_tokens: 0
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1413
1842
|
var Images = class {
|
|
1414
1843
|
constructor(http) {
|
|
1415
1844
|
this.http = http;
|
|
@@ -1467,30 +1896,73 @@ var Images = class {
|
|
|
1467
1896
|
};
|
|
1468
1897
|
|
|
1469
1898
|
// src/modules/functions.ts
|
|
1470
|
-
var Functions = class {
|
|
1471
|
-
constructor(http) {
|
|
1899
|
+
var Functions = class _Functions {
|
|
1900
|
+
constructor(http, functionsUrl) {
|
|
1472
1901
|
this.http = http;
|
|
1902
|
+
this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Derive the subhosting URL from the base URL.
|
|
1906
|
+
* Base URL pattern: https://{appKey}.{region}.insforge.app
|
|
1907
|
+
* Functions URL: https://{appKey}.functions.insforge.app
|
|
1908
|
+
* Only applies to .insforge.app domains.
|
|
1909
|
+
*/
|
|
1910
|
+
static deriveSubhostingUrl(baseUrl) {
|
|
1911
|
+
try {
|
|
1912
|
+
const { hostname } = new URL(baseUrl);
|
|
1913
|
+
if (!hostname.endsWith(".insforge.app")) return void 0;
|
|
1914
|
+
const appKey = hostname.split(".")[0];
|
|
1915
|
+
return `https://${appKey}.functions.insforge.app`;
|
|
1916
|
+
} catch {
|
|
1917
|
+
return void 0;
|
|
1918
|
+
}
|
|
1473
1919
|
}
|
|
1474
1920
|
/**
|
|
1475
1921
|
* Invokes an Edge Function
|
|
1922
|
+
*
|
|
1923
|
+
* If functionsUrl is configured, tries direct subhosting first.
|
|
1924
|
+
* Falls back to proxy URL if subhosting returns 404.
|
|
1925
|
+
*
|
|
1476
1926
|
* @param slug The function slug to invoke
|
|
1477
1927
|
* @param options Request options
|
|
1478
1928
|
*/
|
|
1479
1929
|
async invoke(slug, options = {}) {
|
|
1930
|
+
const { method = "POST", body, headers = {} } = options;
|
|
1931
|
+
if (this.functionsUrl) {
|
|
1932
|
+
try {
|
|
1933
|
+
const data = await this.http.request(method, `${this.functionsUrl}/${slug}`, {
|
|
1934
|
+
body,
|
|
1935
|
+
headers
|
|
1936
|
+
});
|
|
1937
|
+
return { data, error: null };
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
1940
|
+
if (error instanceof InsForgeError && error.statusCode === 404) {
|
|
1941
|
+
} else {
|
|
1942
|
+
return {
|
|
1943
|
+
data: null,
|
|
1944
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1945
|
+
error instanceof Error ? error.message : "Function invocation failed",
|
|
1946
|
+
500,
|
|
1947
|
+
"FUNCTION_ERROR"
|
|
1948
|
+
)
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1480
1953
|
try {
|
|
1481
|
-
const { method = "POST", body, headers = {} } = options;
|
|
1482
1954
|
const path = `/functions/${slug}`;
|
|
1483
|
-
const data = await this.http.request(
|
|
1484
|
-
method,
|
|
1485
|
-
path,
|
|
1486
|
-
{ body, headers }
|
|
1487
|
-
);
|
|
1955
|
+
const data = await this.http.request(method, path, { body, headers });
|
|
1488
1956
|
return { data, error: null };
|
|
1489
1957
|
} catch (error) {
|
|
1958
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
1490
1959
|
return {
|
|
1491
1960
|
data: null,
|
|
1492
|
-
error
|
|
1493
|
-
|
|
1961
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1962
|
+
error instanceof Error ? error.message : "Function invocation failed",
|
|
1963
|
+
500,
|
|
1964
|
+
"FUNCTION_ERROR"
|
|
1965
|
+
)
|
|
1494
1966
|
};
|
|
1495
1967
|
}
|
|
1496
1968
|
}
|
|
@@ -1500,13 +1972,15 @@ var Functions = class {
|
|
|
1500
1972
|
var import_socket = require("socket.io-client");
|
|
1501
1973
|
var CONNECT_TIMEOUT = 1e4;
|
|
1502
1974
|
var Realtime = class {
|
|
1503
|
-
constructor(baseUrl, tokenManager) {
|
|
1975
|
+
constructor(baseUrl, tokenManager, anonKey) {
|
|
1504
1976
|
this.socket = null;
|
|
1505
1977
|
this.connectPromise = null;
|
|
1506
1978
|
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
1507
1979
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
1508
1980
|
this.baseUrl = baseUrl;
|
|
1509
1981
|
this.tokenManager = tokenManager;
|
|
1982
|
+
this.anonKey = anonKey;
|
|
1983
|
+
this.tokenManager.onTokenChange = () => this.onTokenChange();
|
|
1510
1984
|
}
|
|
1511
1985
|
notifyListeners(event, payload) {
|
|
1512
1986
|
const listeners = this.eventListeners.get(event);
|
|
@@ -1531,8 +2005,7 @@ var Realtime = class {
|
|
|
1531
2005
|
return this.connectPromise;
|
|
1532
2006
|
}
|
|
1533
2007
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
1534
|
-
const
|
|
1535
|
-
const token = session?.accessToken;
|
|
2008
|
+
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
1536
2009
|
this.socket = (0, import_socket.io)(this.baseUrl, {
|
|
1537
2010
|
transports: ["websocket"],
|
|
1538
2011
|
auth: token ? { token } : void 0
|
|
@@ -1598,6 +2071,21 @@ var Realtime = class {
|
|
|
1598
2071
|
}
|
|
1599
2072
|
this.subscribedChannels.clear();
|
|
1600
2073
|
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Handle token changes (e.g., after auth refresh)
|
|
2076
|
+
* Updates socket auth so reconnects use the new token
|
|
2077
|
+
* If connected, triggers reconnect to apply new token immediately
|
|
2078
|
+
*/
|
|
2079
|
+
onTokenChange() {
|
|
2080
|
+
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
2081
|
+
if (this.socket) {
|
|
2082
|
+
this.socket.auth = token ? { token } : {};
|
|
2083
|
+
}
|
|
2084
|
+
if (this.socket && (this.socket.connected || this.connectPromise)) {
|
|
2085
|
+
this.socket.disconnect();
|
|
2086
|
+
this.socket.connect();
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
1601
2089
|
/**
|
|
1602
2090
|
* Check if connected to the realtime server
|
|
1603
2091
|
*/
|
|
@@ -1746,8 +2234,15 @@ var Emails = class {
|
|
|
1746
2234
|
);
|
|
1747
2235
|
return { data, error: null };
|
|
1748
2236
|
} catch (error) {
|
|
1749
|
-
|
|
1750
|
-
return {
|
|
2237
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
2238
|
+
return {
|
|
2239
|
+
data: null,
|
|
2240
|
+
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
2241
|
+
error instanceof Error ? error.message : "Email send failed",
|
|
2242
|
+
500,
|
|
2243
|
+
"EMAIL_ERROR"
|
|
2244
|
+
)
|
|
2245
|
+
};
|
|
1751
2246
|
}
|
|
1752
2247
|
}
|
|
1753
2248
|
};
|
|
@@ -1755,31 +2250,30 @@ var Emails = class {
|
|
|
1755
2250
|
// src/client.ts
|
|
1756
2251
|
var InsForgeClient = class {
|
|
1757
2252
|
constructor(config = {}) {
|
|
1758
|
-
|
|
1759
|
-
this.tokenManager = new TokenManager(
|
|
2253
|
+
const logger = new Logger(config.debug);
|
|
2254
|
+
this.tokenManager = new TokenManager();
|
|
2255
|
+
this.http = new HttpClient(config, this.tokenManager, logger);
|
|
1760
2256
|
if (config.edgeFunctionToken) {
|
|
1761
2257
|
this.http.setAuthToken(config.edgeFunctionToken);
|
|
1762
|
-
this.tokenManager.
|
|
1763
|
-
accessToken: config.edgeFunctionToken,
|
|
1764
|
-
user: {}
|
|
1765
|
-
// Will be populated by getCurrentUser()
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
const existingSession = this.tokenManager.getSession();
|
|
1769
|
-
if (existingSession?.accessToken) {
|
|
1770
|
-
this.http.setAuthToken(existingSession.accessToken);
|
|
2258
|
+
this.tokenManager.setAccessToken(config.edgeFunctionToken);
|
|
1771
2259
|
}
|
|
1772
|
-
this.auth = new Auth(this.http, this.tokenManager
|
|
2260
|
+
this.auth = new Auth(this.http, this.tokenManager, {
|
|
2261
|
+
isServerMode: config.isServerMode ?? false
|
|
2262
|
+
});
|
|
1773
2263
|
this.database = new Database(this.http, this.tokenManager);
|
|
1774
2264
|
this.storage = new Storage(this.http);
|
|
1775
2265
|
this.ai = new AI(this.http);
|
|
1776
|
-
this.functions = new Functions(this.http);
|
|
1777
|
-
this.realtime = new Realtime(
|
|
2266
|
+
this.functions = new Functions(this.http, config.functionsUrl);
|
|
2267
|
+
this.realtime = new Realtime(
|
|
2268
|
+
this.http.baseUrl,
|
|
2269
|
+
this.tokenManager,
|
|
2270
|
+
config.anonKey
|
|
2271
|
+
);
|
|
1778
2272
|
this.emails = new Emails(this.http);
|
|
1779
2273
|
}
|
|
1780
2274
|
/**
|
|
1781
2275
|
* Get the underlying HTTP client for custom requests
|
|
1782
|
-
*
|
|
2276
|
+
*
|
|
1783
2277
|
* @example
|
|
1784
2278
|
* ```typescript
|
|
1785
2279
|
* const httpClient = client.getHttpClient();
|
|
@@ -1814,6 +2308,7 @@ var index_default = InsForgeClient;
|
|
|
1814
2308
|
HttpClient,
|
|
1815
2309
|
InsForgeClient,
|
|
1816
2310
|
InsForgeError,
|
|
2311
|
+
Logger,
|
|
1817
2312
|
Realtime,
|
|
1818
2313
|
Storage,
|
|
1819
2314
|
StorageBucket,
|