@cossistant/core 0.0.26 → 0.0.29
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/client.d.ts +48 -6
- package/client.d.ts.map +1 -1
- package/client.js +50 -1
- package/client.js.map +1 -1
- package/conversation.d.ts +3 -0
- package/conversation.d.ts.map +1 -1
- package/index.d.ts +6 -4
- package/index.js +3 -1
- package/package.json +1 -1
- package/realtime-events.d.ts +163 -0
- package/realtime-events.d.ts.map +1 -1
- package/rest-client.d.ts +32 -1
- package/rest-client.d.ts.map +1 -1
- package/rest-client.js +75 -0
- package/rest-client.js.map +1 -1
- package/schemas.d.ts +1 -0
- package/schemas.d.ts.map +1 -1
- package/store/conversations-store.d.ts +6 -6
- package/store/conversations-store.d.ts.map +1 -1
- package/store/conversations-store.js +2 -1
- package/store/conversations-store.js.map +1 -1
- package/store/typing-store.d.ts.map +1 -1
- package/store/typing-store.js +6 -6
- package/store/typing-store.js.map +1 -1
- package/types/src/enums.js +4 -1
- package/types/src/enums.js.map +1 -1
- package/typing-reporter.d.ts +71 -0
- package/typing-reporter.d.ts.map +1 -0
- package/typing-reporter.js +145 -0
- package/typing-reporter.js.map +1 -0
- package/upload-constants.d.ts +40 -0
- package/upload-constants.d.ts.map +1 -0
- package/upload-constants.js +70 -0
- package/upload-constants.js.map +1 -0
- package/upload.d.ts +47 -0
- package/upload.d.ts.map +1 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//#region src/typing-reporter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared typing reporter logic for throttling and scheduling typing events.
|
|
4
|
+
* This is a framework-agnostic utility used by both React widget and dashboard hooks.
|
|
5
|
+
*/
|
|
6
|
+
/** Minimum interval between typing event sends (ms) */
|
|
7
|
+
const TYPING_SEND_INTERVAL_MS = 800;
|
|
8
|
+
/** Keep-alive interval for typing events (ms) */
|
|
9
|
+
const TYPING_KEEP_ALIVE_MS = 4e3;
|
|
10
|
+
/** Delay before auto-stop typing on inactivity (ms) */
|
|
11
|
+
const TYPING_STOP_DELAY_MS = 2e3;
|
|
12
|
+
/** Maximum length for typing preview text */
|
|
13
|
+
const TYPING_PREVIEW_MAX_LENGTH = 2e3;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a typing reporter instance that handles throttling and scheduling
|
|
16
|
+
* of typing events.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const reporter = createTypingReporter({
|
|
21
|
+
* send: async (isTyping, preview) => {
|
|
22
|
+
* await api.sendTypingEvent({ isTyping, preview });
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // On input change
|
|
27
|
+
* reporter.handleInputChange(inputValue);
|
|
28
|
+
*
|
|
29
|
+
* // On submit
|
|
30
|
+
* reporter.handleSubmit();
|
|
31
|
+
*
|
|
32
|
+
* // On unmount
|
|
33
|
+
* reporter.dispose();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function createTypingReporter(config) {
|
|
37
|
+
const { send, sendIntervalMs = TYPING_SEND_INTERVAL_MS, keepAliveMs = TYPING_KEEP_ALIVE_MS, stopDelayMs = TYPING_STOP_DELAY_MS, previewMaxLength = TYPING_PREVIEW_MAX_LENGTH, includePreview = true } = config;
|
|
38
|
+
const state = {
|
|
39
|
+
isActive: false,
|
|
40
|
+
lastSentAt: 0,
|
|
41
|
+
latestPreview: ""
|
|
42
|
+
};
|
|
43
|
+
const timers = {
|
|
44
|
+
keepAlive: null,
|
|
45
|
+
stopTyping: null
|
|
46
|
+
};
|
|
47
|
+
const clearKeepAlive = () => {
|
|
48
|
+
if (timers.keepAlive) {
|
|
49
|
+
clearTimeout(timers.keepAlive);
|
|
50
|
+
timers.keepAlive = null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const clearStopTypingTimeout = () => {
|
|
54
|
+
if (timers.stopTyping) {
|
|
55
|
+
clearTimeout(timers.stopTyping);
|
|
56
|
+
timers.stopTyping = null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const sendTyping = (isTyping) => {
|
|
60
|
+
const preview = includePreview && isTyping ? state.latestPreview : null;
|
|
61
|
+
Promise.resolve(send(isTyping, preview)).catch((error) => {
|
|
62
|
+
console.error("[TypingReporter] Failed to send typing event", error);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const scheduleKeepAlive = () => {
|
|
66
|
+
clearKeepAlive();
|
|
67
|
+
timers.keepAlive = setTimeout(() => {
|
|
68
|
+
if (state.isActive) {
|
|
69
|
+
sendTyping(true);
|
|
70
|
+
scheduleKeepAlive();
|
|
71
|
+
}
|
|
72
|
+
}, keepAliveMs);
|
|
73
|
+
};
|
|
74
|
+
const scheduleStopTyping = () => {
|
|
75
|
+
clearStopTypingTimeout();
|
|
76
|
+
timers.stopTyping = setTimeout(() => {
|
|
77
|
+
if (state.isActive) {
|
|
78
|
+
state.isActive = false;
|
|
79
|
+
clearKeepAlive();
|
|
80
|
+
sendTyping(false);
|
|
81
|
+
}
|
|
82
|
+
}, stopDelayMs);
|
|
83
|
+
};
|
|
84
|
+
const handleInputChange = (value) => {
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
state.latestPreview = trimmed.slice(0, previewMaxLength);
|
|
87
|
+
const now = typeof globalThis !== "undefined" && "Date" in globalThis ? Date.now() : 0;
|
|
88
|
+
if (trimmed.length === 0) {
|
|
89
|
+
if (state.isActive) {
|
|
90
|
+
state.isActive = false;
|
|
91
|
+
state.lastSentAt = now;
|
|
92
|
+
clearKeepAlive();
|
|
93
|
+
clearStopTypingTimeout();
|
|
94
|
+
sendTyping(false);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
scheduleStopTyping();
|
|
99
|
+
if (!state.isActive) {
|
|
100
|
+
state.isActive = true;
|
|
101
|
+
state.lastSentAt = now;
|
|
102
|
+
sendTyping(true);
|
|
103
|
+
scheduleKeepAlive();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (now - state.lastSentAt >= sendIntervalMs) {
|
|
107
|
+
state.lastSentAt = now;
|
|
108
|
+
sendTyping(true);
|
|
109
|
+
scheduleKeepAlive();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const handleSubmit = () => {
|
|
113
|
+
if (!state.isActive) return;
|
|
114
|
+
state.isActive = false;
|
|
115
|
+
state.lastSentAt = typeof globalThis !== "undefined" && "Date" in globalThis ? Date.now() : 0;
|
|
116
|
+
clearKeepAlive();
|
|
117
|
+
clearStopTypingTimeout();
|
|
118
|
+
sendTyping(false);
|
|
119
|
+
};
|
|
120
|
+
const stop = () => {
|
|
121
|
+
if (!state.isActive) return;
|
|
122
|
+
state.isActive = false;
|
|
123
|
+
state.lastSentAt = typeof globalThis !== "undefined" && "Date" in globalThis ? Date.now() : 0;
|
|
124
|
+
clearKeepAlive();
|
|
125
|
+
clearStopTypingTimeout();
|
|
126
|
+
sendTyping(false);
|
|
127
|
+
};
|
|
128
|
+
const dispose = () => {
|
|
129
|
+
if (state.isActive) sendTyping(false);
|
|
130
|
+
clearKeepAlive();
|
|
131
|
+
clearStopTypingTimeout();
|
|
132
|
+
};
|
|
133
|
+
const getState = () => ({ ...state });
|
|
134
|
+
return {
|
|
135
|
+
handleInputChange,
|
|
136
|
+
handleSubmit,
|
|
137
|
+
stop,
|
|
138
|
+
dispose,
|
|
139
|
+
getState
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
export { TYPING_KEEP_ALIVE_MS, TYPING_PREVIEW_MAX_LENGTH, TYPING_SEND_INTERVAL_MS, TYPING_STOP_DELAY_MS, createTypingReporter };
|
|
145
|
+
//# sourceMappingURL=typing-reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typing-reporter.js","names":["state: TypingReporterState","timers: TypingReporterTimers"],"sources":["../src/typing-reporter.ts"],"sourcesContent":["/**\n * Shared typing reporter logic for throttling and scheduling typing events.\n * This is a framework-agnostic utility used by both React widget and dashboard hooks.\n */\n\n/** Minimum interval between typing event sends (ms) */\nexport const TYPING_SEND_INTERVAL_MS = 800;\n\n/** Keep-alive interval for typing events (ms) */\nexport const TYPING_KEEP_ALIVE_MS = 4000;\n\n/** Delay before auto-stop typing on inactivity (ms) */\nexport const TYPING_STOP_DELAY_MS = 2000;\n\n/** Maximum length for typing preview text */\nexport const TYPING_PREVIEW_MAX_LENGTH = 2000;\n\ntype TypingReporterState = {\n\tisActive: boolean;\n\tlastSentAt: number;\n\tlatestPreview: string;\n};\n\ntype TypingReporterTimers = {\n\tkeepAlive: ReturnType<typeof setTimeout> | null;\n\tstopTyping: ReturnType<typeof setTimeout> | null;\n};\n\ntype TypingReporterSendFn = (\n\tisTyping: boolean,\n\tpreview?: string | null\n) => void | Promise<void>;\n\nexport type TypingReporterConfig = {\n\t/** Function to send the typing event */\n\tsend: TypingReporterSendFn;\n\t/** Custom send interval (default: 800ms) */\n\tsendIntervalMs?: number;\n\t/** Custom keep-alive interval (default: 4000ms) */\n\tkeepAliveMs?: number;\n\t/** Custom stop delay (default: 2000ms) */\n\tstopDelayMs?: number;\n\t/** Maximum preview length (default: 2000) */\n\tpreviewMaxLength?: number;\n\t/** Whether to include preview text (default: true) */\n\tincludePreview?: boolean;\n};\n\nexport type TypingReporter = {\n\t/** Call when input value changes */\n\thandleInputChange: (value: string) => void;\n\t/** Call when message is submitted */\n\thandleSubmit: () => void;\n\t/** Force stop typing indicator */\n\tstop: () => void;\n\t/** Clean up timers (call on unmount) */\n\tdispose: () => void;\n\t/** Get current state (for testing) */\n\tgetState: () => TypingReporterState;\n};\n\n/**\n * Creates a typing reporter instance that handles throttling and scheduling\n * of typing events.\n *\n * @example\n * ```ts\n * const reporter = createTypingReporter({\n * send: async (isTyping, preview) => {\n * await api.sendTypingEvent({ isTyping, preview });\n * },\n * });\n *\n * // On input change\n * reporter.handleInputChange(inputValue);\n *\n * // On submit\n * reporter.handleSubmit();\n *\n * // On unmount\n * reporter.dispose();\n * ```\n */\nexport function createTypingReporter(\n\tconfig: TypingReporterConfig\n): TypingReporter {\n\tconst {\n\t\tsend,\n\t\tsendIntervalMs = TYPING_SEND_INTERVAL_MS,\n\t\tkeepAliveMs = TYPING_KEEP_ALIVE_MS,\n\t\tstopDelayMs = TYPING_STOP_DELAY_MS,\n\t\tpreviewMaxLength = TYPING_PREVIEW_MAX_LENGTH,\n\t\tincludePreview = true,\n\t} = config;\n\n\tconst state: TypingReporterState = {\n\t\tisActive: false,\n\t\tlastSentAt: 0,\n\t\tlatestPreview: \"\",\n\t};\n\n\tconst timers: TypingReporterTimers = {\n\t\tkeepAlive: null,\n\t\tstopTyping: null,\n\t};\n\n\tconst clearKeepAlive = () => {\n\t\tif (timers.keepAlive) {\n\t\t\tclearTimeout(timers.keepAlive);\n\t\t\ttimers.keepAlive = null;\n\t\t}\n\t};\n\n\tconst clearStopTypingTimeout = () => {\n\t\tif (timers.stopTyping) {\n\t\t\tclearTimeout(timers.stopTyping);\n\t\t\ttimers.stopTyping = null;\n\t\t}\n\t};\n\n\tconst sendTyping = (isTyping: boolean) => {\n\t\tconst preview = includePreview && isTyping ? state.latestPreview : null;\n\t\tvoid Promise.resolve(send(isTyping, preview)).catch((error) => {\n\t\t\tconsole.error(\"[TypingReporter] Failed to send typing event\", error);\n\t\t});\n\t};\n\n\tconst scheduleKeepAlive = () => {\n\t\tclearKeepAlive();\n\t\ttimers.keepAlive = setTimeout(() => {\n\t\t\tif (state.isActive) {\n\t\t\t\tsendTyping(true);\n\t\t\t\tscheduleKeepAlive();\n\t\t\t}\n\t\t}, keepAliveMs);\n\t};\n\n\tconst scheduleStopTyping = () => {\n\t\tclearStopTypingTimeout();\n\t\ttimers.stopTyping = setTimeout(() => {\n\t\t\tif (state.isActive) {\n\t\t\t\tstate.isActive = false;\n\t\t\t\tclearKeepAlive();\n\t\t\t\tsendTyping(false);\n\t\t\t}\n\t\t}, stopDelayMs);\n\t};\n\n\tconst handleInputChange = (value: string) => {\n\t\tconst trimmed = value.trim();\n\t\tstate.latestPreview = trimmed.slice(0, previewMaxLength);\n\t\tconst now =\n\t\t\ttypeof globalThis !== \"undefined\" && \"Date\" in globalThis\n\t\t\t\t? Date.now()\n\t\t\t\t: 0;\n\n\t\tif (trimmed.length === 0) {\n\t\t\tif (state.isActive) {\n\t\t\t\tstate.isActive = false;\n\t\t\t\tstate.lastSentAt = now;\n\t\t\t\tclearKeepAlive();\n\t\t\t\tclearStopTypingTimeout();\n\t\t\t\tsendTyping(false);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Schedule auto-stop after inactivity\n\t\tscheduleStopTyping();\n\n\t\tif (!state.isActive) {\n\t\t\tstate.isActive = true;\n\t\t\tstate.lastSentAt = now;\n\t\t\tsendTyping(true);\n\t\t\tscheduleKeepAlive();\n\t\t\treturn;\n\t\t}\n\n\t\tif (now - state.lastSentAt >= sendIntervalMs) {\n\t\t\tstate.lastSentAt = now;\n\t\t\tsendTyping(true);\n\t\t\tscheduleKeepAlive();\n\t\t}\n\t};\n\n\tconst handleSubmit = () => {\n\t\tif (!state.isActive) {\n\t\t\treturn;\n\t\t}\n\n\t\tstate.isActive = false;\n\t\tstate.lastSentAt =\n\t\t\ttypeof globalThis !== \"undefined\" && \"Date\" in globalThis\n\t\t\t\t? Date.now()\n\t\t\t\t: 0;\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tsendTyping(false);\n\t};\n\n\tconst stop = () => {\n\t\tif (!state.isActive) {\n\t\t\treturn;\n\t\t}\n\n\t\tstate.isActive = false;\n\t\tstate.lastSentAt =\n\t\t\ttypeof globalThis !== \"undefined\" && \"Date\" in globalThis\n\t\t\t\t? Date.now()\n\t\t\t\t: 0;\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t\tsendTyping(false);\n\t};\n\n\tconst dispose = () => {\n\t\tif (state.isActive) {\n\t\t\tsendTyping(false);\n\t\t}\n\t\tclearKeepAlive();\n\t\tclearStopTypingTimeout();\n\t};\n\n\tconst getState = () => ({ ...state });\n\n\treturn {\n\t\thandleInputChange,\n\t\thandleSubmit,\n\t\tstop,\n\t\tdispose,\n\t\tgetState,\n\t};\n}\n"],"mappings":";;;;;;AAMA,MAAa,0BAA0B;;AAGvC,MAAa,uBAAuB;;AAGpC,MAAa,uBAAuB;;AAGpC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;AAoEzC,SAAgB,qBACf,QACiB;CACjB,MAAM,EACL,MACA,iBAAiB,yBACjB,cAAc,sBACd,cAAc,sBACd,mBAAmB,2BACnB,iBAAiB,SACd;CAEJ,MAAMA,QAA6B;EAClC,UAAU;EACV,YAAY;EACZ,eAAe;EACf;CAED,MAAMC,SAA+B;EACpC,WAAW;EACX,YAAY;EACZ;CAED,MAAM,uBAAuB;AAC5B,MAAI,OAAO,WAAW;AACrB,gBAAa,OAAO,UAAU;AAC9B,UAAO,YAAY;;;CAIrB,MAAM,+BAA+B;AACpC,MAAI,OAAO,YAAY;AACtB,gBAAa,OAAO,WAAW;AAC/B,UAAO,aAAa;;;CAItB,MAAM,cAAc,aAAsB;EACzC,MAAM,UAAU,kBAAkB,WAAW,MAAM,gBAAgB;AACnE,EAAK,QAAQ,QAAQ,KAAK,UAAU,QAAQ,CAAC,CAAC,OAAO,UAAU;AAC9D,WAAQ,MAAM,gDAAgD,MAAM;IACnE;;CAGH,MAAM,0BAA0B;AAC/B,kBAAgB;AAChB,SAAO,YAAY,iBAAiB;AACnC,OAAI,MAAM,UAAU;AACnB,eAAW,KAAK;AAChB,uBAAmB;;KAElB,YAAY;;CAGhB,MAAM,2BAA2B;AAChC,0BAAwB;AACxB,SAAO,aAAa,iBAAiB;AACpC,OAAI,MAAM,UAAU;AACnB,UAAM,WAAW;AACjB,oBAAgB;AAChB,eAAW,MAAM;;KAEhB,YAAY;;CAGhB,MAAM,qBAAqB,UAAkB;EAC5C,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAM,gBAAgB,QAAQ,MAAM,GAAG,iBAAiB;EACxD,MAAM,MACL,OAAO,eAAe,eAAe,UAAU,aAC5C,KAAK,KAAK,GACV;AAEJ,MAAI,QAAQ,WAAW,GAAG;AACzB,OAAI,MAAM,UAAU;AACnB,UAAM,WAAW;AACjB,UAAM,aAAa;AACnB,oBAAgB;AAChB,4BAAwB;AACxB,eAAW,MAAM;;AAElB;;AAID,sBAAoB;AAEpB,MAAI,CAAC,MAAM,UAAU;AACpB,SAAM,WAAW;AACjB,SAAM,aAAa;AACnB,cAAW,KAAK;AAChB,sBAAmB;AACnB;;AAGD,MAAI,MAAM,MAAM,cAAc,gBAAgB;AAC7C,SAAM,aAAa;AACnB,cAAW,KAAK;AAChB,sBAAmB;;;CAIrB,MAAM,qBAAqB;AAC1B,MAAI,CAAC,MAAM,SACV;AAGD,QAAM,WAAW;AACjB,QAAM,aACL,OAAO,eAAe,eAAe,UAAU,aAC5C,KAAK,KAAK,GACV;AACJ,kBAAgB;AAChB,0BAAwB;AACxB,aAAW,MAAM;;CAGlB,MAAM,aAAa;AAClB,MAAI,CAAC,MAAM,SACV;AAGD,QAAM,WAAW;AACjB,QAAM,aACL,OAAO,eAAe,eAAe,UAAU,aAC5C,KAAK,KAAK,GACV;AACJ,kBAAgB;AAChB,0BAAwB;AACxB,aAAW,MAAM;;CAGlB,MAAM,gBAAgB;AACrB,MAAI,MAAM,SACT,YAAW,MAAM;AAElB,kBAAgB;AAChB,0BAAwB;;CAGzB,MAAM,kBAAkB,EAAE,GAAG,OAAO;AAEpC,QAAO;EACN;EACA;EACA;EACA;EACA;EACA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/upload-constants.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* File upload constants for cost/API protection.
|
|
4
|
+
* These limits are enforced on both client and server side.
|
|
5
|
+
*/
|
|
6
|
+
/** Maximum file size in bytes (5 MB) */
|
|
7
|
+
declare const MAX_FILE_SIZE: number;
|
|
8
|
+
/** Maximum number of files per message */
|
|
9
|
+
declare const MAX_FILES_PER_MESSAGE = 3;
|
|
10
|
+
/** Allowed MIME types for file uploads */
|
|
11
|
+
declare const ALLOWED_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf", "text/plain", "text/csv", "text/markdown", "application/zip"];
|
|
12
|
+
/** Human-readable file type descriptions for error messages */
|
|
13
|
+
declare const ALLOWED_FILE_TYPES_DESCRIPTION = "images (JPEG, PNG, GIF, WebP), PDF, text files (TXT, CSV, MD), and ZIP archives";
|
|
14
|
+
/** Accept string for file input elements */
|
|
15
|
+
declare const FILE_INPUT_ACCEPT: string;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a MIME type is allowed for upload
|
|
18
|
+
*/
|
|
19
|
+
declare function isAllowedMimeType(mimeType: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a file is an image based on MIME type
|
|
22
|
+
*/
|
|
23
|
+
declare function isImageMimeType(mimeType: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Format file size for display
|
|
26
|
+
*/
|
|
27
|
+
declare function formatFileSize(bytes: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Validate a file against upload constraints
|
|
30
|
+
* @returns null if valid, error message if invalid
|
|
31
|
+
*/
|
|
32
|
+
declare function validateFile(file: File): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Validate multiple files against upload constraints
|
|
35
|
+
* @returns null if all valid, error message if any invalid
|
|
36
|
+
*/
|
|
37
|
+
declare function validateFiles(files: File[]): string | null;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { ALLOWED_FILE_TYPES_DESCRIPTION, ALLOWED_MIME_TYPES, FILE_INPUT_ACCEPT, MAX_FILES_PER_MESSAGE, MAX_FILE_SIZE, formatFileSize, isAllowedMimeType, isImageMimeType, validateFile, validateFiles };
|
|
40
|
+
//# sourceMappingURL=upload-constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-constants.d.ts","names":[],"sources":["../src/upload-constants.ts"],"sourcesContent":[],"mappings":";;AAMA;AAGA;AAGA;AAiBA;AAIa,cA3BA,aA2BgD,EAAA,MAAA;AAK7D;AAOgB,cApCH,qBAAA,GAoCkB,CAAA;AAO/B;AAcgB,cAtDH,kBAsD0B,EAAA,SAAA,CAAA,YAAA,EAAA,WAAA,EAAA,WAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,eAAA,EAAA,iBAAA,CAAA;AAgBvC;cArDa,8BAAA;;cAIA;;;;iBAKG,iBAAA;;;;iBAOA,eAAA;;;;iBAOA,cAAA;;;;;iBAcA,YAAA,OAAmB;;;;;iBAgBnB,aAAA,QAAqB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/upload-constants.ts
|
|
2
|
+
/**
|
|
3
|
+
* File upload constants for cost/API protection.
|
|
4
|
+
* These limits are enforced on both client and server side.
|
|
5
|
+
*/
|
|
6
|
+
/** Maximum file size in bytes (5 MB) */
|
|
7
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
8
|
+
/** Maximum number of files per message */
|
|
9
|
+
const MAX_FILES_PER_MESSAGE = 3;
|
|
10
|
+
/** Allowed MIME types for file uploads */
|
|
11
|
+
const ALLOWED_MIME_TYPES = [
|
|
12
|
+
"image/jpeg",
|
|
13
|
+
"image/png",
|
|
14
|
+
"image/gif",
|
|
15
|
+
"image/webp",
|
|
16
|
+
"application/pdf",
|
|
17
|
+
"text/plain",
|
|
18
|
+
"text/csv",
|
|
19
|
+
"text/markdown",
|
|
20
|
+
"application/zip"
|
|
21
|
+
];
|
|
22
|
+
/** Human-readable file type descriptions for error messages */
|
|
23
|
+
const ALLOWED_FILE_TYPES_DESCRIPTION = "images (JPEG, PNG, GIF, WebP), PDF, text files (TXT, CSV, MD), and ZIP archives";
|
|
24
|
+
/** Accept string for file input elements */
|
|
25
|
+
const FILE_INPUT_ACCEPT = ALLOWED_MIME_TYPES.join(",");
|
|
26
|
+
/**
|
|
27
|
+
* Check if a MIME type is allowed for upload
|
|
28
|
+
*/
|
|
29
|
+
function isAllowedMimeType(mimeType) {
|
|
30
|
+
return ALLOWED_MIME_TYPES.includes(mimeType);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if a file is an image based on MIME type
|
|
34
|
+
*/
|
|
35
|
+
function isImageMimeType(mimeType) {
|
|
36
|
+
return mimeType.startsWith("image/");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format file size for display
|
|
40
|
+
*/
|
|
41
|
+
function formatFileSize(bytes) {
|
|
42
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
43
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
44
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate a file against upload constraints
|
|
48
|
+
* @returns null if valid, error message if invalid
|
|
49
|
+
*/
|
|
50
|
+
function validateFile(file) {
|
|
51
|
+
if (file.size > MAX_FILE_SIZE) return `File "${file.name}" exceeds maximum size of ${formatFileSize(MAX_FILE_SIZE)}`;
|
|
52
|
+
if (!isAllowedMimeType(file.type)) return `File type "${file.type || "unknown"}" is not allowed. Allowed types: ${ALLOWED_FILE_TYPES_DESCRIPTION}`;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate multiple files against upload constraints
|
|
57
|
+
* @returns null if all valid, error message if any invalid
|
|
58
|
+
*/
|
|
59
|
+
function validateFiles(files) {
|
|
60
|
+
if (files.length > MAX_FILES_PER_MESSAGE) return `Cannot attach more than ${MAX_FILES_PER_MESSAGE} files per message`;
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
const error = validateFile(file);
|
|
63
|
+
if (error) return error;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { ALLOWED_FILE_TYPES_DESCRIPTION, ALLOWED_MIME_TYPES, FILE_INPUT_ACCEPT, MAX_FILES_PER_MESSAGE, MAX_FILE_SIZE, formatFileSize, isAllowedMimeType, isImageMimeType, validateFile, validateFiles };
|
|
70
|
+
//# sourceMappingURL=upload-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-constants.js","names":[],"sources":["../src/upload-constants.ts"],"sourcesContent":["/**\n * File upload constants for cost/API protection.\n * These limits are enforced on both client and server side.\n */\n\n/** Maximum file size in bytes (5 MB) */\nexport const MAX_FILE_SIZE = 5 * 1024 * 1024;\n\n/** Maximum number of files per message */\nexport const MAX_FILES_PER_MESSAGE = 3;\n\n/** Allowed MIME types for file uploads */\nexport const ALLOWED_MIME_TYPES = [\n\t// Images\n\t\"image/jpeg\",\n\t\"image/png\",\n\t\"image/gif\",\n\t\"image/webp\",\n\t// Documents\n\t\"application/pdf\",\n\t// Text files\n\t\"text/plain\",\n\t\"text/csv\",\n\t\"text/markdown\",\n\t// Archives\n\t\"application/zip\",\n] as const;\n\n/** Human-readable file type descriptions for error messages */\nexport const ALLOWED_FILE_TYPES_DESCRIPTION =\n\t\"images (JPEG, PNG, GIF, WebP), PDF, text files (TXT, CSV, MD), and ZIP archives\";\n\n/** Accept string for file input elements */\nexport const FILE_INPUT_ACCEPT = ALLOWED_MIME_TYPES.join(\",\");\n\n/**\n * Check if a MIME type is allowed for upload\n */\nexport function isAllowedMimeType(mimeType: string): boolean {\n\treturn (ALLOWED_MIME_TYPES as readonly string[]).includes(mimeType);\n}\n\n/**\n * Check if a file is an image based on MIME type\n */\nexport function isImageMimeType(mimeType: string): boolean {\n\treturn mimeType.startsWith(\"image/\");\n}\n\n/**\n * Format file size for display\n */\nexport function formatFileSize(bytes: number): string {\n\tif (bytes < 1024) {\n\t\treturn `${bytes} B`;\n\t}\n\tif (bytes < 1024 * 1024) {\n\t\treturn `${(bytes / 1024).toFixed(1)} KB`;\n\t}\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * Validate a file against upload constraints\n * @returns null if valid, error message if invalid\n */\nexport function validateFile(file: File): string | null {\n\tif (file.size > MAX_FILE_SIZE) {\n\t\treturn `File \"${file.name}\" exceeds maximum size of ${formatFileSize(MAX_FILE_SIZE)}`;\n\t}\n\n\tif (!isAllowedMimeType(file.type)) {\n\t\treturn `File type \"${file.type || \"unknown\"}\" is not allowed. Allowed types: ${ALLOWED_FILE_TYPES_DESCRIPTION}`;\n\t}\n\n\treturn null;\n}\n\n/**\n * Validate multiple files against upload constraints\n * @returns null if all valid, error message if any invalid\n */\nexport function validateFiles(files: File[]): string | null {\n\tif (files.length > MAX_FILES_PER_MESSAGE) {\n\t\treturn `Cannot attach more than ${MAX_FILES_PER_MESSAGE} files per message`;\n\t}\n\n\tfor (const file of files) {\n\t\tconst error = validateFile(file);\n\t\tif (error) {\n\t\t\treturn error;\n\t\t}\n\t}\n\n\treturn null;\n}\n"],"mappings":";;;;;;AAMA,MAAa,gBAAgB,IAAI,OAAO;;AAGxC,MAAa,wBAAwB;;AAGrC,MAAa,qBAAqB;CAEjC;CACA;CACA;CACA;CAEA;CAEA;CACA;CACA;CAEA;CACA;;AAGD,MAAa,iCACZ;;AAGD,MAAa,oBAAoB,mBAAmB,KAAK,IAAI;;;;AAK7D,SAAgB,kBAAkB,UAA2B;AAC5D,QAAQ,mBAAyC,SAAS,SAAS;;;;;AAMpE,SAAgB,gBAAgB,UAA2B;AAC1D,QAAO,SAAS,WAAW,SAAS;;;;;AAMrC,SAAgB,eAAe,OAAuB;AACrD,KAAI,QAAQ,KACX,QAAO,GAAG,MAAM;AAEjB,KAAI,QAAQ,OAAO,KAClB,QAAO,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAErC,QAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,EAAE,CAAC;;;;;;AAO9C,SAAgB,aAAa,MAA2B;AACvD,KAAI,KAAK,OAAO,cACf,QAAO,SAAS,KAAK,KAAK,4BAA4B,eAAe,cAAc;AAGpF,KAAI,CAAC,kBAAkB,KAAK,KAAK,CAChC,QAAO,cAAc,KAAK,QAAQ,UAAU,mCAAmC;AAGhF,QAAO;;;;;;AAOR,SAAgB,cAAc,OAA8B;AAC3D,KAAI,MAAM,SAAS,sBAClB,QAAO,2BAA2B,sBAAsB;AAGzD,MAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,QAAQ,aAAa,KAAK;AAChC,MAAI,MACH,QAAO;;AAIT,QAAO"}
|
package/upload.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "@hono/zod-openapi";
|
|
2
|
+
|
|
3
|
+
//#region ../types/src/api/upload.d.ts
|
|
4
|
+
|
|
5
|
+
declare const generateUploadUrlRequestSchema: z.ZodObject<{
|
|
6
|
+
contentType: z.ZodString;
|
|
7
|
+
websiteId: z.ZodString;
|
|
8
|
+
scope: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9
|
+
type: z.ZodLiteral<"conversation">;
|
|
10
|
+
conversationId: z.ZodString;
|
|
11
|
+
organizationId: z.ZodString;
|
|
12
|
+
websiteId: z.ZodString;
|
|
13
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
14
|
+
type: z.ZodLiteral<"user">;
|
|
15
|
+
userId: z.ZodString;
|
|
16
|
+
organizationId: z.ZodString;
|
|
17
|
+
websiteId: z.ZodString;
|
|
18
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
19
|
+
type: z.ZodLiteral<"contact">;
|
|
20
|
+
contactId: z.ZodString;
|
|
21
|
+
organizationId: z.ZodString;
|
|
22
|
+
websiteId: z.ZodString;
|
|
23
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
24
|
+
type: z.ZodLiteral<"visitor">;
|
|
25
|
+
visitorId: z.ZodString;
|
|
26
|
+
organizationId: z.ZodString;
|
|
27
|
+
websiteId: z.ZodString;
|
|
28
|
+
}, z.core.$strip>], "type">;
|
|
29
|
+
path: z.ZodOptional<z.ZodString>;
|
|
30
|
+
fileName: z.ZodOptional<z.ZodString>;
|
|
31
|
+
fileExtension: z.ZodOptional<z.ZodString>;
|
|
32
|
+
useCdn: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
expiresInSeconds: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
type GenerateUploadUrlRequest = z.infer<typeof generateUploadUrlRequestSchema>;
|
|
36
|
+
declare const generateUploadUrlResponseSchema: z.ZodObject<{
|
|
37
|
+
uploadUrl: z.ZodURL;
|
|
38
|
+
key: z.ZodString;
|
|
39
|
+
bucket: z.ZodString;
|
|
40
|
+
expiresAt: z.ZodString;
|
|
41
|
+
contentType: z.ZodString;
|
|
42
|
+
publicUrl: z.ZodURL;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
type GenerateUploadUrlResponse = z.infer<typeof generateUploadUrlResponseSchema>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { GenerateUploadUrlRequest, GenerateUploadUrlResponse };
|
|
47
|
+
//# sourceMappingURL=upload.d.ts.map
|
package/upload.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","names":[],"sources":["../../types/src/api/upload.ts"],"sourcesContent":[],"mappings":";;;;AAkMY,cAvEC,8BAwEL,EAxEmC,CAAA,CAAA,SAwEnC,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAxCI,wBAAA,GAA2B,CAAA,CAAE,aACjC;cAGK,iCAA+B,CAAA,CAAA;;;;;;;;KAmChC,yBAAA,GAA4B,CAAA,CAAE,aAClC"}
|