@byteoniclabs/intake 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +138 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +51 -0
- package/dist/react/index.d.ts +51 -0
- package/dist/react/index.js +392 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +349 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/server.d.mts +42 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +105 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +80 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types-1XzE88JK.d.mts +13 -0
- package/dist/types-1XzE88JK.d.ts +13 -0
- package/package.json +62 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// src/react/useForm.ts
|
|
2
|
+
import { useState, useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
// src/react/provider.tsx
|
|
5
|
+
import React, { createContext, useContext, useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
// src/client.ts
|
|
8
|
+
var ByteonicClient = class {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
if (!config.apiKey) {
|
|
11
|
+
throw new Error("[Byteonic Intake] API Key is required");
|
|
12
|
+
}
|
|
13
|
+
this.apiKey = config.apiKey;
|
|
14
|
+
this.baseUrl = config.baseUrl || "https://intake.byteoniclabs.com/api/external";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Submit form data to Byteonic Intake
|
|
18
|
+
* @param formSlug The slug of the form to submit to
|
|
19
|
+
* @param data The form data (Record<string, any> or FormData)
|
|
20
|
+
*/
|
|
21
|
+
async submit(formSlug, data) {
|
|
22
|
+
const url = `${this.baseUrl}/forms/${formSlug}/submit`;
|
|
23
|
+
const meta = {
|
|
24
|
+
sdk_version: "1.0.0",
|
|
25
|
+
source_url: typeof window !== "undefined" ? window.location.href : "server",
|
|
26
|
+
submission_source: "byteonic_intake_sdk"
|
|
27
|
+
};
|
|
28
|
+
let fetchOptions = {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
if (typeof FormData !== "undefined" && data instanceof FormData) {
|
|
35
|
+
data.append("_meta", JSON.stringify(meta));
|
|
36
|
+
fetchOptions.body = data;
|
|
37
|
+
} else {
|
|
38
|
+
const payload = data;
|
|
39
|
+
payload._meta = meta;
|
|
40
|
+
fetchOptions.headers = {
|
|
41
|
+
...fetchOptions.headers,
|
|
42
|
+
"Content-Type": "application/json"
|
|
43
|
+
};
|
|
44
|
+
fetchOptions.body = JSON.stringify(payload);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(url, fetchOptions);
|
|
48
|
+
const responseData = await response.json().catch(() => ({}));
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(responseData.error || responseData.message || "Submission failed");
|
|
51
|
+
}
|
|
52
|
+
return responseData;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: error.message || "An unexpected error occurred"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/react/provider.tsx
|
|
63
|
+
var ByteonicContext = createContext(void 0);
|
|
64
|
+
function ByteonicProvider({ apiKey, baseUrl, children }) {
|
|
65
|
+
const client = useMemo(() => new ByteonicClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
66
|
+
return /* @__PURE__ */ React.createElement(ByteonicContext.Provider, { value: client }, children);
|
|
67
|
+
}
|
|
68
|
+
function useByteonicClient() {
|
|
69
|
+
const context = useContext(ByteonicContext);
|
|
70
|
+
if (!context) {
|
|
71
|
+
throw new Error("useByteonicClient must be used within a ByteonicProvider");
|
|
72
|
+
}
|
|
73
|
+
return context;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/react/useForm.ts
|
|
77
|
+
function useByteonicIntake(options) {
|
|
78
|
+
const formSlug = typeof options === "string" ? options : options.formSlug;
|
|
79
|
+
const configApiKey = typeof options === "string" ? void 0 : options.apiKey;
|
|
80
|
+
const configBaseUrl = typeof options === "string" ? void 0 : options.baseUrl;
|
|
81
|
+
let contextClient;
|
|
82
|
+
try {
|
|
83
|
+
contextClient = useByteonicClient();
|
|
84
|
+
} catch (e) {
|
|
85
|
+
}
|
|
86
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
87
|
+
const [isSuccess, setIsSuccess] = useState(false);
|
|
88
|
+
const [error, setError] = useState(null);
|
|
89
|
+
const [response, setResponse] = useState(null);
|
|
90
|
+
const submit = useCallback(async (data) => {
|
|
91
|
+
setIsLoading(true);
|
|
92
|
+
setError(null);
|
|
93
|
+
setIsSuccess(false);
|
|
94
|
+
try {
|
|
95
|
+
let client = contextClient;
|
|
96
|
+
if (!client) {
|
|
97
|
+
if (!configApiKey) {
|
|
98
|
+
const envKey = typeof process !== "undefined" && process.env?.NEXT_PUBLIC_BYTEONIC_API_KEY || typeof globalThis.import?.meta !== "undefined" && globalThis.import?.meta?.env?.VITE_BYTEONIC_API_KEY || typeof import.meta !== "undefined" && import.meta.env?.VITE_BYTEONIC_API_KEY;
|
|
99
|
+
if (envKey) {
|
|
100
|
+
client = new ByteonicClient({ apiKey: envKey, baseUrl: configBaseUrl });
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error("Byteonic Intake: apiKey is required via <ByteonicProvider>, directly in useByteonicIntake, or as an environment variable (NEXT_PUBLIC_BYTEONIC_API_KEY / VITE_BYTEONIC_API_KEY)");
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
client = new ByteonicClient({ apiKey: configApiKey, baseUrl: configBaseUrl });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let payload = data;
|
|
109
|
+
if (data && typeof data.preventDefault === "function") {
|
|
110
|
+
data.preventDefault();
|
|
111
|
+
payload = new FormData(data.currentTarget);
|
|
112
|
+
}
|
|
113
|
+
const result = await client.submit(formSlug, payload);
|
|
114
|
+
setResponse(result);
|
|
115
|
+
if (result.success !== false) {
|
|
116
|
+
setIsSuccess(true);
|
|
117
|
+
} else {
|
|
118
|
+
setError(result.error || "Submission failed");
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
setError(err.message || "An unexpected error occurred");
|
|
123
|
+
return { success: false, error: err.message };
|
|
124
|
+
} finally {
|
|
125
|
+
setIsLoading(false);
|
|
126
|
+
}
|
|
127
|
+
}, [formSlug, contextClient, configApiKey, configBaseUrl]);
|
|
128
|
+
return {
|
|
129
|
+
submit,
|
|
130
|
+
isLoading,
|
|
131
|
+
isSuccess,
|
|
132
|
+
error,
|
|
133
|
+
response
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/react/honeypot.tsx
|
|
138
|
+
import React2 from "react";
|
|
139
|
+
function ByteonicHoneypot() {
|
|
140
|
+
return /* @__PURE__ */ React2.createElement("div", { style: { display: "none", position: "absolute", left: "-9999px", opacity: 0 }, "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("label", { htmlFor: "_gotcha" }, "Please leave this field blank"), /* @__PURE__ */ React2.createElement("input", { type: "text", name: "_gotcha", id: "_gotcha", tabIndex: -1, autoComplete: "off" }));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/react/useChatbot.ts
|
|
144
|
+
import { useState as useState2, useCallback as useCallback2, useEffect } from "react";
|
|
145
|
+
|
|
146
|
+
// src/chat.ts
|
|
147
|
+
var ByteonicChatClient = class {
|
|
148
|
+
constructor(config) {
|
|
149
|
+
if (!config.apiKey) {
|
|
150
|
+
throw new Error("[Byteonic Intake] API Key is required");
|
|
151
|
+
}
|
|
152
|
+
this.apiKey = config.apiKey;
|
|
153
|
+
this.baseUrl = config.baseUrl || "https://backend.intake.byteoniclabs.com/api/v1/chat";
|
|
154
|
+
}
|
|
155
|
+
async getConfig(botId) {
|
|
156
|
+
const url = `${this.baseUrl}/config?id=${botId}`;
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(url, {
|
|
159
|
+
headers: {
|
|
160
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) return null;
|
|
164
|
+
return await response.json();
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async generate(botId, sessionId, message, history = [], options) {
|
|
170
|
+
const url = `${this.baseUrl}/generate`;
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch(url, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: {
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify({
|
|
179
|
+
chatbot_id: botId,
|
|
180
|
+
session_id: sessionId,
|
|
181
|
+
message,
|
|
182
|
+
history
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const errorData = await response.json().catch(() => ({}));
|
|
187
|
+
throw new Error(errorData.error || "Generation failed");
|
|
188
|
+
}
|
|
189
|
+
const reader = response.body?.getReader();
|
|
190
|
+
const decoder = new TextDecoder("utf-8");
|
|
191
|
+
let fullMessage = "";
|
|
192
|
+
if (reader) {
|
|
193
|
+
let done = false;
|
|
194
|
+
let buffer = "";
|
|
195
|
+
while (!done) {
|
|
196
|
+
const { value, done: readerDone } = await reader.read();
|
|
197
|
+
done = readerDone;
|
|
198
|
+
if (value) {
|
|
199
|
+
buffer += decoder.decode(value, { stream: true });
|
|
200
|
+
const lines = buffer.split("\n");
|
|
201
|
+
buffer = lines.pop() || "";
|
|
202
|
+
for (const line of lines) {
|
|
203
|
+
if (line.startsWith("data: ") && !line.includes("[DONE]")) {
|
|
204
|
+
try {
|
|
205
|
+
const data = JSON.parse(line.slice(6));
|
|
206
|
+
const content = data.choices?.[0]?.delta?.content;
|
|
207
|
+
if (content) {
|
|
208
|
+
fullMessage += content;
|
|
209
|
+
if (options?.onChunk) options.onChunk(fullMessage);
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { success: true, message: fullMessage.trim() || "No response generated." };
|
|
219
|
+
} catch (err) {
|
|
220
|
+
return { success: false, error: err.message || "Network error" };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/react/useChatbot.ts
|
|
226
|
+
function useByteonicChatbot(options) {
|
|
227
|
+
const botId = typeof options === "string" ? options : options.botId;
|
|
228
|
+
const configApiKey = typeof options === "string" ? void 0 : options.apiKey;
|
|
229
|
+
const configBaseUrl = typeof options === "string" ? void 0 : options.baseUrl;
|
|
230
|
+
const configStream = typeof options === "string" ? false : options.stream ?? true;
|
|
231
|
+
let contextClient;
|
|
232
|
+
try {
|
|
233
|
+
contextClient = useByteonicClient();
|
|
234
|
+
} catch (e) {
|
|
235
|
+
}
|
|
236
|
+
const [client, setClient] = useState2(null);
|
|
237
|
+
const [messages, setMessages] = useState2([]);
|
|
238
|
+
const [config, setConfig] = useState2(null);
|
|
239
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
240
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
241
|
+
const [streamedMessage, setStreamedMessage] = useState2("");
|
|
242
|
+
const [sessionId, setSessionId] = useState2("");
|
|
243
|
+
const [isInitialized, setIsInitialized] = useState2(false);
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (typeof window !== "undefined") {
|
|
246
|
+
const saved = localStorage.getItem(`byteonic_messages_${botId}`);
|
|
247
|
+
if (saved) {
|
|
248
|
+
try {
|
|
249
|
+
const parsed = JSON.parse(saved);
|
|
250
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
251
|
+
setMessages(parsed);
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
setIsInitialized(true);
|
|
258
|
+
}, [botId]);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
let sid = typeof window !== "undefined" ? localStorage.getItem(`byteonic_chat_${botId}`) : null;
|
|
261
|
+
if (!sid) {
|
|
262
|
+
sid = Math.random().toString(36).substring(2, 15);
|
|
263
|
+
if (typeof window !== "undefined") localStorage.setItem(`byteonic_chat_${botId}`, sid);
|
|
264
|
+
}
|
|
265
|
+
setSessionId(sid);
|
|
266
|
+
let resolvedClient = null;
|
|
267
|
+
const finalApiKey = configApiKey || contextClient?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_BYTEONIC_API_KEY || typeof globalThis.import?.meta !== "undefined" && globalThis.import?.meta?.env?.VITE_BYTEONIC_API_KEY || typeof import.meta !== "undefined" && import.meta.env?.VITE_BYTEONIC_API_KEY;
|
|
268
|
+
if (finalApiKey) {
|
|
269
|
+
resolvedClient = new ByteonicChatClient({ apiKey: finalApiKey, baseUrl: configBaseUrl });
|
|
270
|
+
setClient(resolvedClient);
|
|
271
|
+
} else {
|
|
272
|
+
console.warn("Byteonic Chatbot: No API Key provided. Chat will not function.");
|
|
273
|
+
}
|
|
274
|
+
if (resolvedClient && isInitialized) {
|
|
275
|
+
resolvedClient.getConfig(botId).then((cfg) => {
|
|
276
|
+
if (cfg) {
|
|
277
|
+
setConfig(cfg);
|
|
278
|
+
setMessages((prev) => {
|
|
279
|
+
if (prev.length === 0 && cfg.welcomeMessage) {
|
|
280
|
+
return [{ role: "assistant", content: cfg.welcomeMessage }];
|
|
281
|
+
}
|
|
282
|
+
return prev;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}, [botId, configApiKey, configBaseUrl, isInitialized]);
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (isInitialized && typeof window !== "undefined") {
|
|
290
|
+
localStorage.setItem(`byteonic_messages_${botId}`, JSON.stringify(messages));
|
|
291
|
+
}
|
|
292
|
+
}, [messages, botId, isInitialized]);
|
|
293
|
+
const sendMessage = useCallback2(async (content) => {
|
|
294
|
+
if (!client || !content.trim()) return;
|
|
295
|
+
const userMsg = { role: "user", content };
|
|
296
|
+
const newHistory = [...messages, userMsg];
|
|
297
|
+
setMessages(newHistory);
|
|
298
|
+
setIsLoading(true);
|
|
299
|
+
if (configStream) {
|
|
300
|
+
setIsStreaming(true);
|
|
301
|
+
setStreamedMessage("");
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const res = await client.generate(botId, sessionId, content, messages, {
|
|
305
|
+
onChunk: configStream ? (chunk) => setStreamedMessage(chunk) : void 0
|
|
306
|
+
});
|
|
307
|
+
if (configStream) {
|
|
308
|
+
setIsStreaming(false);
|
|
309
|
+
setStreamedMessage("");
|
|
310
|
+
}
|
|
311
|
+
if (res.success && res.message) {
|
|
312
|
+
setMessages([...newHistory, { role: "assistant", content: res.message }]);
|
|
313
|
+
} else {
|
|
314
|
+
setMessages([...newHistory, { role: "assistant", content: res.error || "Sorry, I encountered an error." }]);
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
if (configStream) setIsStreaming(false);
|
|
318
|
+
setMessages([...newHistory, { role: "assistant", content: "Network error." }]);
|
|
319
|
+
} finally {
|
|
320
|
+
setIsLoading(false);
|
|
321
|
+
}
|
|
322
|
+
}, [client, messages, botId, sessionId, configStream]);
|
|
323
|
+
const clearHistory = useCallback2(() => {
|
|
324
|
+
setMessages(config?.welcomeMessage ? [{ role: "assistant", content: config.welcomeMessage }] : []);
|
|
325
|
+
const sid = Math.random().toString(36).substring(2, 15);
|
|
326
|
+
setSessionId(sid);
|
|
327
|
+
if (typeof window !== "undefined") {
|
|
328
|
+
localStorage.setItem(`byteonic_chat_${botId}`, sid);
|
|
329
|
+
localStorage.removeItem(`byteonic_messages_${botId}`);
|
|
330
|
+
}
|
|
331
|
+
}, [botId, config]);
|
|
332
|
+
return {
|
|
333
|
+
messages,
|
|
334
|
+
sendMessage,
|
|
335
|
+
isLoading,
|
|
336
|
+
isStreaming,
|
|
337
|
+
streamedMessage,
|
|
338
|
+
config,
|
|
339
|
+
clearHistory
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
export {
|
|
343
|
+
ByteonicHoneypot,
|
|
344
|
+
ByteonicProvider,
|
|
345
|
+
useByteonicChatbot,
|
|
346
|
+
useByteonicClient,
|
|
347
|
+
useByteonicIntake
|
|
348
|
+
};
|
|
349
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/useForm.ts","../../src/react/provider.tsx","../../src/client.ts","../../src/react/honeypot.tsx","../../src/react/useChatbot.ts","../../src/chat.ts"],"sourcesContent":["import { useState, useCallback } from 'react';\r\nimport { useByteonicClient } from './provider';\r\nimport { SubmissionResponse } from '../types';\r\nimport { ByteonicClient } from '../client';\r\n\r\nexport interface UseByteonicIntakeOptions {\r\n formSlug: string;\r\n apiKey?: string;\r\n baseUrl?: string;\r\n}\r\n\r\nexport function useByteonicIntake<T = Record<string, any>>(options: UseByteonicIntakeOptions | string) {\r\n const formSlug = typeof options === 'string' ? options : options.formSlug;\r\n const configApiKey = typeof options === 'string' ? undefined : options.apiKey;\r\n const configBaseUrl = typeof options === 'string' ? undefined : options.baseUrl;\r\n\r\n let contextClient: ByteonicClient | undefined;\r\n try {\r\n contextClient = useByteonicClient();\r\n } catch (e) {\r\n // Suppress error if outside provider\r\n }\r\n\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [response, setResponse] = useState<SubmissionResponse | null>(null);\r\n\r\n const submit = useCallback(async (data: T | FormData | React.FormEvent<HTMLFormElement>) => {\r\n setIsLoading(true);\r\n setError(null);\r\n setIsSuccess(false);\r\n\r\n try {\r\n let client = contextClient;\r\n \r\n if (!client) {\r\n if (!configApiKey) {\r\n const envKey = \r\n (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BYTEONIC_API_KEY) ||\r\n (typeof (globalThis as any).import?.meta !== 'undefined' && (globalThis as any).import?.meta?.env?.VITE_BYTEONIC_API_KEY) ||\r\n (typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_BYTEONIC_API_KEY);\r\n\r\n if (envKey) {\r\n client = new ByteonicClient({ apiKey: envKey as string, baseUrl: configBaseUrl });\r\n } else {\r\n throw new Error('Byteonic Intake: apiKey is required via <ByteonicProvider>, directly in useByteonicIntake, or as an environment variable (NEXT_PUBLIC_BYTEONIC_API_KEY / VITE_BYTEONIC_API_KEY)');\r\n }\r\n } else {\r\n client = new ByteonicClient({ apiKey: configApiKey, baseUrl: configBaseUrl });\r\n }\r\n }\r\n\r\n let payload: any = data;\r\n if (data && typeof (data as any).preventDefault === 'function') {\r\n (data as any).preventDefault();\r\n payload = new FormData((data as any).currentTarget);\r\n }\r\n\r\n const result = await client.submit(formSlug, payload);\r\n \r\n setResponse(result);\r\n if (result.success !== false) {\r\n setIsSuccess(true);\r\n } else {\r\n setError(result.error || 'Submission failed');\r\n }\r\n return result;\r\n } catch (err: any) {\r\n setError(err.message || 'An unexpected error occurred');\r\n return { success: false, error: err.message };\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n }, [formSlug, contextClient, configApiKey, configBaseUrl]);\r\n\r\n return {\r\n submit,\r\n isLoading,\r\n isSuccess,\r\n error,\r\n response\r\n };\r\n}\r\n","import React, { createContext, useContext, ReactNode, useMemo } from 'react';\r\nimport { ByteonicClient } from '../client';\r\nimport { ByteonicConfig } from '../types';\r\n\r\nconst ByteonicContext = createContext<ByteonicClient | undefined>(undefined);\r\n\r\nexport interface ByteonicProviderProps extends ByteonicConfig {\r\n children: ReactNode;\r\n}\r\n\r\nexport function ByteonicProvider({ apiKey, baseUrl, children }: ByteonicProviderProps) {\r\n const client = useMemo(() => new ByteonicClient({ apiKey, baseUrl }), [apiKey, baseUrl]);\r\n\r\n return (\r\n <ByteonicContext.Provider value={client}>\r\n {children}\r\n </ByteonicContext.Provider>\r\n );\r\n}\r\n\r\nexport function useByteonicClient(): ByteonicClient {\r\n const context = useContext(ByteonicContext);\r\n if (!context) {\r\n throw new Error('useByteonicClient must be used within a ByteonicProvider');\r\n }\r\n return context;\r\n}\r\n","import { ByteonicConfig, SubmissionResponse } from './types';\r\n\r\nexport class ByteonicClient {\r\n private apiKey: string;\r\n private baseUrl: string;\r\n\r\n constructor(config: ByteonicConfig) {\r\n if (!config.apiKey) {\r\n throw new Error('[Byteonic Intake] API Key is required');\r\n }\r\n this.apiKey = config.apiKey;\r\n this.baseUrl = config.baseUrl || 'https://intake.byteoniclabs.com/api/external';\r\n }\r\n\r\n /**\r\n * Submit form data to Byteonic Intake\r\n * @param formSlug The slug of the form to submit to\r\n * @param data The form data (Record<string, any> or FormData)\r\n */\r\n async submit(formSlug: string, data: Record<string, any> | FormData): Promise<SubmissionResponse> {\r\n const url = `${this.baseUrl}/forms/${formSlug}/submit`;\r\n \r\n // Auto-track metadata\r\n const meta = {\r\n sdk_version: '1.0.0',\r\n source_url: typeof window !== 'undefined' ? window.location.href : 'server',\r\n submission_source: 'byteonic_intake_sdk'\r\n };\r\n\r\n let fetchOptions: RequestInit = {\r\n method: 'POST',\r\n headers: {\r\n 'Authorization': `Bearer ${this.apiKey}`\r\n }\r\n };\r\n\r\n if (typeof FormData !== 'undefined' && data instanceof FormData) {\r\n // Native File uploads require FormData to remain untouched.\r\n // Do NOT set Content-Type header, the browser sets the multi-part boundary automatically.\r\n data.append('_meta', JSON.stringify(meta));\r\n fetchOptions.body = data;\r\n } else {\r\n // Standard JSON payload\r\n const payload = data as Record<string, any>;\r\n payload._meta = meta;\r\n \r\n fetchOptions.headers = {\r\n ...fetchOptions.headers,\r\n 'Content-Type': 'application/json'\r\n };\r\n fetchOptions.body = JSON.stringify(payload);\r\n }\r\n\r\n try {\r\n const response = await fetch(url, fetchOptions);\r\n\r\n const responseData = await response.json().catch(() => ({}));\r\n \r\n if (!response.ok) {\r\n throw new Error(responseData.error || responseData.message || 'Submission failed');\r\n }\r\n \r\n return responseData;\r\n } catch (error: any) {\r\n return {\r\n success: false,\r\n error: error.message || 'An unexpected error occurred'\r\n };\r\n }\r\n }\r\n}\r\n","import React from 'react';\n\n/**\n * A hidden input field used to trick bots into filling it out.\n * If this field is submitted, Byteonic Intake will ignore the submission, protecting you from spam.\n * \n * Simply place `<ByteonicHoneypot />` anywhere inside your `<form>` element.\n */\nexport function ByteonicHoneypot() {\n return (\n <div style={{ display: 'none', position: 'absolute', left: '-9999px', opacity: 0 }} aria-hidden=\"true\">\n <label htmlFor=\"_gotcha\">Please leave this field blank</label>\n <input type=\"text\" name=\"_gotcha\" id=\"_gotcha\" tabIndex={-1} autoComplete=\"off\" />\n </div>\n );\n}\n","import { useState, useCallback, useEffect } from 'react';\nimport { useByteonicClient } from './provider';\nimport { ByteonicChatClient, ChatMessage, ChatbotConfig } from '../chat';\n\nexport interface UseByteonicChatbotOptions {\n botId: string;\n apiKey?: string;\n baseUrl?: string;\n stream?: boolean;\n}\n\nexport function useByteonicChatbot(options: UseByteonicChatbotOptions | string) {\n const botId = typeof options === 'string' ? options : options.botId;\n const configApiKey = typeof options === 'string' ? undefined : options.apiKey;\n const configBaseUrl = typeof options === 'string' ? undefined : options.baseUrl;\n const configStream = typeof options === 'string' ? false : (options.stream ?? true);\n\n let contextClient: any;\n try {\n contextClient = useByteonicClient();\n } catch (e) {\n // Suppress error if outside provider\n }\n\n const [client, setClient] = useState<ByteonicChatClient | null>(null);\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [config, setConfig] = useState<ChatbotConfig | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [streamedMessage, setStreamedMessage] = useState('');\n const [sessionId, setSessionId] = useState<string>('');\n const [isInitialized, setIsInitialized] = useState(false);\n\n // Load from local storage exclusively on the client after hydration\n useEffect(() => {\n if (typeof window !== 'undefined') {\n const saved = localStorage.getItem(`byteonic_messages_${botId}`);\n if (saved) {\n try {\n const parsed = JSON.parse(saved);\n if (Array.isArray(parsed) && parsed.length > 0) {\n setMessages(parsed);\n }\n } catch (e) {}\n }\n }\n setIsInitialized(true);\n }, [botId]);\n\n useEffect(() => {\n // Generate a random session ID if not exists (using localStorage to persist across tabs/reloads)\n let sid = typeof window !== 'undefined' ? localStorage.getItem(`byteonic_chat_${botId}`) : null;\n if (!sid) {\n sid = Math.random().toString(36).substring(2, 15);\n if (typeof window !== 'undefined') localStorage.setItem(`byteonic_chat_${botId}`, sid);\n }\n setSessionId(sid);\n\n let resolvedClient: ByteonicChatClient | null = null;\n \n // Attempt to pull apiKey from context, then config, then env\n const finalApiKey = configApiKey || (contextClient as any)?.apiKey || \n (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BYTEONIC_API_KEY) ||\n (typeof (globalThis as any).import?.meta !== 'undefined' && (globalThis as any).import?.meta?.env?.VITE_BYTEONIC_API_KEY) ||\n (typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_BYTEONIC_API_KEY);\n\n if (finalApiKey) {\n resolvedClient = new ByteonicChatClient({ apiKey: finalApiKey as string, baseUrl: configBaseUrl });\n setClient(resolvedClient);\n } else {\n console.warn('Byteonic Chatbot: No API Key provided. Chat will not function.');\n }\n\n if (resolvedClient && isInitialized) {\n resolvedClient.getConfig(botId).then(cfg => {\n if (cfg) {\n setConfig(cfg);\n // Only set welcome message if we don't have stored messages\n setMessages(prev => {\n if (prev.length === 0 && cfg.welcomeMessage) {\n return [{ role: 'assistant', content: cfg.welcomeMessage }];\n }\n return prev;\n });\n }\n });\n }\n }, [botId, configApiKey, configBaseUrl, isInitialized]);\n\n // Persist messages to localStorage whenever they change\n useEffect(() => {\n if (isInitialized && typeof window !== 'undefined') {\n localStorage.setItem(`byteonic_messages_${botId}`, JSON.stringify(messages));\n }\n }, [messages, botId, isInitialized]);\n\n const sendMessage = useCallback(async (content: string) => {\n if (!client || !content.trim()) return;\n\n const userMsg: ChatMessage = { role: 'user', content };\n const newHistory = [...messages, userMsg];\n setMessages(newHistory);\n setIsLoading(true);\n\n if (configStream) {\n setIsStreaming(true);\n setStreamedMessage('');\n }\n\n try {\n const res = await client.generate(botId, sessionId, content, messages, {\n onChunk: configStream ? (chunk: string) => setStreamedMessage(chunk) : undefined\n });\n \n if (configStream) {\n setIsStreaming(false);\n setStreamedMessage('');\n }\n\n if (res.success && res.message) {\n setMessages([...newHistory, { role: 'assistant', content: res.message }]);\n } else {\n setMessages([...newHistory, { role: 'assistant', content: res.error || 'Sorry, I encountered an error.' }]);\n }\n } catch (e) {\n if (configStream) setIsStreaming(false);\n setMessages([...newHistory, { role: 'assistant', content: 'Network error.' }]);\n } finally {\n setIsLoading(false);\n }\n }, [client, messages, botId, sessionId, configStream]);\n\n const clearHistory = useCallback(() => {\n setMessages(config?.welcomeMessage ? [{ role: 'assistant', content: config.welcomeMessage }] : []);\n const sid = Math.random().toString(36).substring(2, 15);\n setSessionId(sid);\n if (typeof window !== 'undefined') {\n localStorage.setItem(`byteonic_chat_${botId}`, sid);\n localStorage.removeItem(`byteonic_messages_${botId}`);\n }\n }, [botId, config]);\n\n return {\n messages,\n sendMessage,\n isLoading,\n isStreaming,\n streamedMessage,\n config,\n clearHistory\n };\n}\n","import { ByteonicConfig } from './types';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface ChatbotConfig {\n botId: string;\n name?: string;\n theme?: string;\n welcomeMessage?: string;\n [key: string]: any;\n}\n\nexport interface GenerateResponse {\n success: boolean;\n message?: string;\n error?: string;\n}\n\nexport interface GenerateOptions {\n onChunk?: (fullMessage: string) => void;\n}\n\nexport class ByteonicChatClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(config: ByteonicConfig) {\n if (!config.apiKey) {\n throw new Error('[Byteonic Intake] API Key is required');\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://backend.intake.byteoniclabs.com/api/v1/chat';\n }\n\n async getConfig(botId: string): Promise<ChatbotConfig | null> {\n const url = `${this.baseUrl}/config?id=${botId}`;\n try {\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`\n }\n });\n if (!response.ok) return null;\n return await response.json();\n } catch {\n return null;\n }\n }\n\n async generate(botId: string, sessionId: string, message: string, history: ChatMessage[] = [], options?: GenerateOptions): Promise<GenerateResponse> {\n const url = `${this.baseUrl}/generate`;\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify({ \n chatbot_id: botId, \n session_id: sessionId, \n message, \n history \n })\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.error || 'Generation failed');\n }\n\n const reader = response.body?.getReader();\n const decoder = new TextDecoder('utf-8');\n let fullMessage = '';\n\n if (reader) {\n let done = false;\n let buffer = '';\n while (!done) {\n const { value, done: readerDone } = await reader.read();\n done = readerDone;\n if (value) {\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep incomplete lines in buffer\n\n for (const line of lines) {\n if (line.startsWith('data: ') && !line.includes('[DONE]')) {\n try {\n const data = JSON.parse(line.slice(6));\n const content = data.choices?.[0]?.delta?.content;\n if (content) {\n fullMessage += content;\n if (options?.onChunk) options.onChunk(fullMessage);\n }\n } catch (e) {\n // Ignore partial chunk parse errors\n }\n }\n }\n }\n }\n }\n\n return { success: true, message: fullMessage.trim() || 'No response generated.' };\n } catch (err: any) {\n return { success: false, error: err.message || 'Network error' };\n }\n }\n}\n"],"mappings":"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","names":["React","useState","useCallback","useState","useCallback"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { S as SubmissionResponse } from './types-1XzE88JK.mjs';
|
|
2
|
+
|
|
3
|
+
interface ServerConfig {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/**
|
|
7
|
+
* If your Byteonic Intake dashboard has Domain Whitelisting enabled,
|
|
8
|
+
* you MUST provide a matching origin here to bypass the block.
|
|
9
|
+
* Example: "https://yourdomain.com"
|
|
10
|
+
*/
|
|
11
|
+
origin?: string;
|
|
12
|
+
}
|
|
13
|
+
interface ServerSubmitOptions {
|
|
14
|
+
/**
|
|
15
|
+
* The actual IP address of the end-user (e.g., from req.headers['x-forwarded-for']).
|
|
16
|
+
* This ensures your Intake dashboard shows the user's location, not your server's location.
|
|
17
|
+
*/
|
|
18
|
+
userIp?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The actual User-Agent of the end-user browser.
|
|
21
|
+
*/
|
|
22
|
+
userAgent?: string;
|
|
23
|
+
/**
|
|
24
|
+
* A custom source URL to tag this submission with.
|
|
25
|
+
*/
|
|
26
|
+
sourceUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
declare class ByteonicServerClient {
|
|
29
|
+
private apiKey;
|
|
30
|
+
private baseUrl;
|
|
31
|
+
private origin?;
|
|
32
|
+
constructor(config: ServerConfig);
|
|
33
|
+
/**
|
|
34
|
+
* Submit form data to Byteonic Intake from a Node.js Server
|
|
35
|
+
* @param formSlug The slug of the form to submit to
|
|
36
|
+
* @param data The form data (Record<string, any> or FormData)
|
|
37
|
+
* @param options Server-specific options for tracking user IP/Agent
|
|
38
|
+
*/
|
|
39
|
+
submit(formSlug: string, data: Record<string, any> | FormData, options?: ServerSubmitOptions): Promise<SubmissionResponse>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { ByteonicServerClient, type ServerConfig, type ServerSubmitOptions };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { S as SubmissionResponse } from './types-1XzE88JK.js';
|
|
2
|
+
|
|
3
|
+
interface ServerConfig {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/**
|
|
7
|
+
* If your Byteonic Intake dashboard has Domain Whitelisting enabled,
|
|
8
|
+
* you MUST provide a matching origin here to bypass the block.
|
|
9
|
+
* Example: "https://yourdomain.com"
|
|
10
|
+
*/
|
|
11
|
+
origin?: string;
|
|
12
|
+
}
|
|
13
|
+
interface ServerSubmitOptions {
|
|
14
|
+
/**
|
|
15
|
+
* The actual IP address of the end-user (e.g., from req.headers['x-forwarded-for']).
|
|
16
|
+
* This ensures your Intake dashboard shows the user's location, not your server's location.
|
|
17
|
+
*/
|
|
18
|
+
userIp?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The actual User-Agent of the end-user browser.
|
|
21
|
+
*/
|
|
22
|
+
userAgent?: string;
|
|
23
|
+
/**
|
|
24
|
+
* A custom source URL to tag this submission with.
|
|
25
|
+
*/
|
|
26
|
+
sourceUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
declare class ByteonicServerClient {
|
|
29
|
+
private apiKey;
|
|
30
|
+
private baseUrl;
|
|
31
|
+
private origin?;
|
|
32
|
+
constructor(config: ServerConfig);
|
|
33
|
+
/**
|
|
34
|
+
* Submit form data to Byteonic Intake from a Node.js Server
|
|
35
|
+
* @param formSlug The slug of the form to submit to
|
|
36
|
+
* @param data The form data (Record<string, any> or FormData)
|
|
37
|
+
* @param options Server-specific options for tracking user IP/Agent
|
|
38
|
+
*/
|
|
39
|
+
submit(formSlug: string, data: Record<string, any> | FormData, options?: ServerSubmitOptions): Promise<SubmissionResponse>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { ByteonicServerClient, type ServerConfig, type ServerSubmitOptions };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
ByteonicServerClient: () => ByteonicServerClient
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(server_exports);
|
|
26
|
+
var ByteonicServerClient = class {
|
|
27
|
+
constructor(config) {
|
|
28
|
+
if (!config.apiKey) {
|
|
29
|
+
throw new Error("[Byteonic Intake] API Key is required");
|
|
30
|
+
}
|
|
31
|
+
this.apiKey = config.apiKey;
|
|
32
|
+
this.baseUrl = config.baseUrl || "https://intake.byteoniclabs.com/api/external";
|
|
33
|
+
this.origin = config.origin;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Submit form data to Byteonic Intake from a Node.js Server
|
|
37
|
+
* @param formSlug The slug of the form to submit to
|
|
38
|
+
* @param data The form data (Record<string, any> or FormData)
|
|
39
|
+
* @param options Server-specific options for tracking user IP/Agent
|
|
40
|
+
*/
|
|
41
|
+
async submit(formSlug, data, options = {}) {
|
|
42
|
+
const url = `${this.baseUrl}/forms/${formSlug}/submit`;
|
|
43
|
+
const meta = {
|
|
44
|
+
sdk_version: "1.0.0-server",
|
|
45
|
+
source_url: options.sourceUrl || "server",
|
|
46
|
+
submission_source: "byteonic_intake_sdk_server"
|
|
47
|
+
};
|
|
48
|
+
let fetchHeaders = {
|
|
49
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
50
|
+
};
|
|
51
|
+
if (this.origin) {
|
|
52
|
+
fetchHeaders["Origin"] = this.origin;
|
|
53
|
+
fetchHeaders["Referer"] = this.origin;
|
|
54
|
+
}
|
|
55
|
+
if (options.userIp) {
|
|
56
|
+
fetchHeaders["x-forwarded-for"] = options.userIp;
|
|
57
|
+
}
|
|
58
|
+
if (options.userAgent) {
|
|
59
|
+
fetchHeaders["User-Agent"] = options.userAgent;
|
|
60
|
+
}
|
|
61
|
+
let fetchOptions = {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: fetchHeaders
|
|
64
|
+
};
|
|
65
|
+
if (typeof FormData !== "undefined" && data instanceof FormData) {
|
|
66
|
+
data.append("_meta", JSON.stringify(meta));
|
|
67
|
+
fetchOptions.body = data;
|
|
68
|
+
} else {
|
|
69
|
+
const payload = data;
|
|
70
|
+
payload._meta = meta;
|
|
71
|
+
fetchOptions.headers = {
|
|
72
|
+
...fetchOptions.headers,
|
|
73
|
+
"Content-Type": "application/json"
|
|
74
|
+
};
|
|
75
|
+
fetchOptions.body = JSON.stringify(payload);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(url, fetchOptions);
|
|
79
|
+
const responseData = await response.json().catch(() => ({}));
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: responseData.error || `Submission failed with status ${response.status}`,
|
|
84
|
+
status: response.status
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
data: responseData,
|
|
90
|
+
status: response.status
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: error.message || "Network request failed",
|
|
96
|
+
status: 500
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
102
|
+
0 && (module.exports = {
|
|
103
|
+
ByteonicServerClient
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { SubmissionResponse } from './types';\n\nexport interface ServerConfig {\n apiKey: string;\n baseUrl?: string;\n /**\n * If your Byteonic Intake dashboard has Domain Whitelisting enabled,\n * you MUST provide a matching origin here to bypass the block.\n * Example: \"https://yourdomain.com\"\n */\n origin?: string;\n}\n\nexport interface ServerSubmitOptions {\n /**\n * The actual IP address of the end-user (e.g., from req.headers['x-forwarded-for']).\n * This ensures your Intake dashboard shows the user's location, not your server's location.\n */\n userIp?: string;\n /**\n * The actual User-Agent of the end-user browser.\n */\n userAgent?: string;\n /**\n * A custom source URL to tag this submission with.\n */\n sourceUrl?: string;\n}\n\nexport class ByteonicServerClient {\n private apiKey: string;\n private baseUrl: string;\n private origin?: string;\n\n constructor(config: ServerConfig) {\n if (!config.apiKey) {\n throw new Error('[Byteonic Intake] API Key is required');\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://intake.byteoniclabs.com/api/external';\n this.origin = config.origin;\n }\n\n /**\n * Submit form data to Byteonic Intake from a Node.js Server\n * @param formSlug The slug of the form to submit to\n * @param data The form data (Record<string, any> or FormData)\n * @param options Server-specific options for tracking user IP/Agent\n */\n async submit(formSlug: string, data: Record<string, any> | FormData, options: ServerSubmitOptions = {}): Promise<SubmissionResponse> {\n const url = `${this.baseUrl}/forms/${formSlug}/submit`;\n \n // Auto-track metadata for servers\n const meta = {\n sdk_version: '1.0.0-server',\n source_url: options.sourceUrl || 'server',\n submission_source: 'byteonic_intake_sdk_server'\n };\n\n let fetchHeaders: Record<string, string> = {\n 'Authorization': `Bearer ${this.apiKey}`\n };\n\n // Spoof origin if provided to bypass domain whitelists safely\n if (this.origin) {\n fetchHeaders['Origin'] = this.origin;\n fetchHeaders['Referer'] = this.origin;\n }\n \n // Forward user headers if provided\n if (options.userIp) {\n fetchHeaders['x-forwarded-for'] = options.userIp;\n }\n if (options.userAgent) {\n fetchHeaders['User-Agent'] = options.userAgent;\n }\n\n let fetchOptions: RequestInit = {\n method: 'POST',\n headers: fetchHeaders\n };\n\n if (typeof FormData !== 'undefined' && data instanceof FormData) {\n // Native Node 18+ FormData support\n data.append('_meta', JSON.stringify(meta));\n fetchOptions.body = data;\n } else {\n // Standard JSON payload\n const payload = data as Record<string, any>;\n payload._meta = meta;\n \n fetchOptions.headers = {\n ...fetchOptions.headers,\n 'Content-Type': 'application/json'\n };\n fetchOptions.body = JSON.stringify(payload);\n }\n\n try {\n const response = await fetch(url, fetchOptions);\n const responseData = await response.json().catch(() => ({}));\n \n if (!response.ok) {\n return {\n success: false,\n error: responseData.error || `Submission failed with status ${response.status}`,\n status: response.status\n };\n }\n\n return {\n success: true,\n data: responseData,\n status: response.status\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message || 'Network request failed',\n status: 500\n };\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BO,IAAM,uBAAN,MAA2B;AAAA,EAKhC,YAAY,QAAsB;AAChC,QAAI,CAAC,OAAO,QAAQ;AAClB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AACA,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,SAAS,OAAO;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,UAAkB,MAAsC,UAA+B,CAAC,GAAgC;AACnI,UAAM,MAAM,GAAG,KAAK,OAAO,UAAU,QAAQ;AAG7C,UAAM,OAAO;AAAA,MACX,aAAa;AAAA,MACb,YAAY,QAAQ,aAAa;AAAA,MACjC,mBAAmB;AAAA,IACrB;AAEA,QAAI,eAAuC;AAAA,MACzC,iBAAiB,UAAU,KAAK,MAAM;AAAA,IACxC;AAGA,QAAI,KAAK,QAAQ;AACf,mBAAa,QAAQ,IAAI,KAAK;AAC9B,mBAAa,SAAS,IAAI,KAAK;AAAA,IACjC;AAGA,QAAI,QAAQ,QAAQ;AAClB,mBAAa,iBAAiB,IAAI,QAAQ;AAAA,IAC5C;AACA,QAAI,QAAQ,WAAW;AACrB,mBAAa,YAAY,IAAI,QAAQ;AAAA,IACvC;AAEA,QAAI,eAA4B;AAAA,MAC9B,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAEA,QAAI,OAAO,aAAa,eAAe,gBAAgB,UAAU;AAE/D,WAAK,OAAO,SAAS,KAAK,UAAU,IAAI,CAAC;AACzC,mBAAa,OAAO;AAAA,IACtB,OAAO;AAEL,YAAM,UAAU;AAChB,cAAQ,QAAQ;AAEhB,mBAAa,UAAU;AAAA,QACrB,GAAG,aAAa;AAAA,QAChB,gBAAgB;AAAA,MAClB;AACA,mBAAa,OAAO,KAAK,UAAU,OAAO;AAAA,IAC5C;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAC9C,YAAM,eAAe,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAE3D,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,aAAa,SAAS,iCAAiC,SAAS,MAAM;AAAA,UAC7E,QAAQ,SAAS;AAAA,QACnB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,MACnB;AAAA,IACF,SAAS,OAAY;AACnB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,MAAM,WAAW;AAAA,QACxB,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|