socket-function 0.140.0 → 0.141.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.
@@ -54,6 +54,7 @@ export interface CallType<FncT extends FncType = FncType, FncName extends string
54
54
  functionName: FncName;
55
55
  args: unknown[];
56
56
  }
57
+ // NOTE: If you are in an HTTP call, and want the request, use getCurrentHTTPRequest at the start of your endpoint.
57
58
  export interface FullCallType<FncT extends FncType = FncType, FncName extends string = string> extends CallType<FncT, FncName> {
58
59
  nodeId: string;
59
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.140.0",
3
+ "version": "0.141.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -9,6 +9,7 @@ import { cacheLimited, lazy } from "../src/caching";
9
9
  import { formatNumber } from "../src/formatting/format";
10
10
  import { requireMain } from "./require";
11
11
  import path from "path";
12
+ import { getContentTypeFromBuffer, getExtContentType } from "./extMapper";
12
13
 
13
14
  const COMPRESS_CACHE_SIZE = 1024 * 1024 * 128;
14
15
 
@@ -89,6 +90,10 @@ let beforeEntryText: (string | (() => Promise<string>))[] = [];
89
90
  function injectHTMLBeforeStartup(text: string | (() => Promise<string>)) {
90
91
  beforeEntryText.push(text);
91
92
  }
93
+ let staticRoots: string[] = [];
94
+ function addStaticRoot(root: string) {
95
+ staticRoots.push(root);
96
+ }
92
97
 
93
98
  type GetModulesResult = ReturnType<RequireControllerBase["getModules"]> extends Promise<infer T> ? T : never;
94
99
  export type GetModulesArgs = Parameters<RequireControllerBase["getModules"]>;
@@ -106,12 +111,49 @@ class RequireControllerBase {
106
111
  requireCalls?: string[];
107
112
  cacheTime?: number;
108
113
  }) {
114
+ let { requireCalls, cacheTime } = config || {};
115
+ let httpRequest = getCurrentHTTPRequest();
116
+ let headers: Record<string, string> = {
117
+ "Content-Type": "text/html"
118
+ };
119
+ if (cacheTime) {
120
+ headers["Cache-Control"] = `max-age=${Math.floor(cacheTime / 1000)}`;
121
+ }
122
+ if (httpRequest) {
123
+ let urlObj = new URL("https://" + httpRequest.headers.host + httpRequest.url);
124
+ if (urlObj.pathname !== "/" && !requireCalls?.length) {
125
+ if (urlObj.pathname.includes("..")) {
126
+ throw new Error(`Invalid path, may not have ".." in the path, ${urlObj.pathname}`);
127
+ }
128
+
129
+
130
+ let result: Buffer | undefined;
131
+ for (let root of staticRoots) {
132
+ let resolved = root + urlObj.pathname;
133
+ if (fs.existsSync(resolved)) {
134
+ result = await fs.promises.readFile(resolved);
135
+ break;
136
+ }
137
+ }
138
+ if (result) {
139
+ let contentType = getContentTypeFromBuffer(result);
140
+ if (!contentType) {
141
+ let ext = path.extname(urlObj.pathname);
142
+ contentType = getExtContentType(ext);
143
+ }
144
+ headers["Content-Type"] = contentType;
145
+ return setHTTPResultHeaders(result, headers);
146
+ }
147
+
148
+ throw new Error(`Static file not found, ${urlObj.pathname}, have static roots: ${JSON.stringify(staticRoots)}`);
149
+ }
150
+
151
+ }
109
152
  if (!this.rootResolvePath) {
110
153
  let dir = path.resolve(".");
111
154
  dir = dir.replaceAll("\\", "/");
112
155
  this.rootResolvePath = dir;
113
156
  }
114
- let { requireCalls, cacheTime } = config || {};
115
157
  let result = resolvedHTMLFile();
116
158
  if (beforeEntryText.length > 0) {
117
159
  let resolved: string[] = [];
@@ -142,12 +184,6 @@ class RequireControllerBase {
142
184
  } else {
143
185
  result = result.replace(ENTRY_TEMPLATE, "");
144
186
  }
145
- let headers: Record<string, string> = {
146
- "Content-Type": "text/html"
147
- };
148
- if (cacheTime) {
149
- headers["Cache-Control"] = `max-age=${Math.floor(cacheTime / 1000)}`;
150
- }
151
187
  return setHTTPResultHeaders(Buffer.from(result), headers);
152
188
  }
153
189
 
@@ -405,6 +441,7 @@ export const RequireController = SocketFunction.register(
405
441
  statics: {
406
442
  injectHTMLBeforeStartup,
407
443
  addMapGetModules,
444
+ addStaticRoot,
408
445
  }
409
446
  }
410
447
  );
@@ -0,0 +1,236 @@
1
+ export function getExtContentType(ext: string): string {
2
+ // Images
3
+ if (ext === ".svg") return "image/svg+xml";
4
+ if (ext === ".ico") return "image/x-icon";
5
+ if (ext === ".png") return "image/png";
6
+ if (ext === ".jpg") return "image/jpeg";
7
+ if (ext === ".jpeg") return "image/jpeg";
8
+ if (ext === ".gif") return "image/gif";
9
+ if (ext === ".webp") return "image/webp";
10
+ if (ext === ".avif") return "image/avif";
11
+ if (ext === ".heic") return "image/heic";
12
+ if (ext === ".heif") return "image/heif";
13
+ if (ext === ".bmp") return "image/bmp";
14
+ if (ext === ".tiff") return "image/tiff";
15
+ if (ext === ".tif") return "image/tiff";
16
+ if (ext === ".jxl") return "image/jxl";
17
+
18
+ // Audio
19
+ if (ext === ".mp3") return "audio/mpeg";
20
+ if (ext === ".wav") return "audio/wav";
21
+ if (ext === ".ogg") return "audio/ogg";
22
+ if (ext === ".m4a") return "audio/mp4";
23
+ if (ext === ".aac") return "audio/aac";
24
+ if (ext === ".flac") return "audio/flac";
25
+ if (ext === ".wma") return "audio/x-ms-wma";
26
+ if (ext === ".opus") return "audio/opus";
27
+
28
+ // Video
29
+ if (ext === ".mp4") return "video/mp4";
30
+ if (ext === ".webm") return "video/webm";
31
+ if (ext === ".mkv") return "video/x-matroska";
32
+ if (ext === ".avi") return "video/x-msvideo";
33
+ if (ext === ".mov") return "video/quicktime";
34
+ if (ext === ".wmv") return "video/x-ms-wmv";
35
+ if (ext === ".flv") return "video/x-flv";
36
+ if (ext === ".m4v") return "video/x-m4v";
37
+
38
+ // Web files
39
+ if (ext === ".html") return "text/html";
40
+ if (ext === ".htm") return "text/html";
41
+ if (ext === ".css") return "text/css";
42
+ if (ext === ".js") return "text/javascript";
43
+ if (ext === ".mjs") return "text/javascript";
44
+ if (ext === ".json") return "application/json";
45
+ if (ext === ".xml") return "application/xml";
46
+ if (ext === ".rss") return "application/rss+xml";
47
+ if (ext === ".atom") return "application/atom+xml";
48
+
49
+ // Documents
50
+ if (ext === ".pdf") return "application/pdf";
51
+ if (ext === ".doc") return "application/msword";
52
+ if (ext === ".docx") return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
53
+ if (ext === ".xls") return "application/vnd.ms-excel";
54
+ if (ext === ".xlsx") return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
55
+ if (ext === ".ppt") return "application/vnd.ms-powerpoint";
56
+ if (ext === ".pptx") return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
57
+ if (ext === ".odt") return "application/vnd.oasis.opendocument.text";
58
+ if (ext === ".ods") return "application/vnd.oasis.opendocument.spreadsheet";
59
+ if (ext === ".odp") return "application/vnd.oasis.opendocument.presentation";
60
+ if (ext === ".rtf") return "application/rtf";
61
+
62
+ // Text files
63
+ if (ext === ".txt") return "text/plain";
64
+ if (ext === ".md") return "text/markdown";
65
+ if (ext === ".csv") return "text/csv";
66
+ if (ext === ".log") return "text/plain";
67
+
68
+ // Programming files
69
+ if (ext === ".ts") return "text/typescript";
70
+ if (ext === ".tsx") return "text/typescript";
71
+ if (ext === ".jsx") return "text/javascript";
72
+ if (ext === ".py") return "text/x-python";
73
+ if (ext === ".java") return "text/x-java-source";
74
+ if (ext === ".c") return "text/x-c";
75
+ if (ext === ".cpp") return "text/x-c++";
76
+ if (ext === ".h") return "text/x-c";
77
+ if (ext === ".hpp") return "text/x-c++";
78
+ if (ext === ".cs") return "text/x-csharp";
79
+ if (ext === ".php") return "text/x-php";
80
+ if (ext === ".rb") return "text/x-ruby";
81
+ if (ext === ".go") return "text/x-go";
82
+ if (ext === ".rs") return "text/x-rust";
83
+ if (ext === ".swift") return "text/x-swift";
84
+ if (ext === ".kt") return "text/x-kotlin";
85
+ if (ext === ".scala") return "text/x-scala";
86
+ if (ext === ".sh") return "text/x-shellscript";
87
+ if (ext === ".bat") return "text/x-msdos-batch";
88
+ if (ext === ".ps1") return "text/x-powershell";
89
+
90
+ // Archives
91
+ if (ext === ".zip") return "application/zip";
92
+ if (ext === ".rar") return "application/vnd.rar";
93
+ if (ext === ".tar") return "application/x-tar";
94
+ if (ext === ".gz") return "application/gzip";
95
+ if (ext === ".bz2") return "application/x-bzip2";
96
+ if (ext === ".7z") return "application/x-7z-compressed";
97
+ if (ext === ".xz") return "application/x-xz";
98
+
99
+ // Fonts
100
+ if (ext === ".ttf") return "font/ttf";
101
+ if (ext === ".otf") return "font/otf";
102
+ if (ext === ".woff") return "font/woff";
103
+ if (ext === ".woff2") return "font/woff2";
104
+ if (ext === ".eot") return "application/vnd.ms-fontobject";
105
+
106
+ // Other common formats
107
+ if (ext === ".epub") return "application/epub+zip";
108
+ if (ext === ".mobi") return "application/x-mobipocket-ebook";
109
+ if (ext === ".apk") return "application/vnd.android.package-archive";
110
+ if (ext === ".dmg") return "application/x-apple-diskimage";
111
+ if (ext === ".iso") return "application/x-iso9660-image";
112
+ if (ext === ".exe") return "application/x-msdownload";
113
+ if (ext === ".msi") return "application/x-msi";
114
+ if (ext === ".deb") return "application/x-debian-package";
115
+ if (ext === ".rpm") return "application/x-rpm";
116
+
117
+ console.warn(`Unknown extension, ${ext}`);
118
+ return "text/plain";
119
+ }
120
+
121
+ export function getContentTypeFromBuffer(buffer: Buffer): string | undefined {
122
+ if (buffer.length < 4) return undefined;
123
+
124
+ // Helper function to check if buffer starts with specific bytes
125
+ const startsWith = (bytes: number[]): boolean => {
126
+ if (buffer.length < bytes.length) return false;
127
+ for (let i = 0; i < bytes.length; i++) {
128
+ if (buffer[i] !== bytes[i]) return false;
129
+ }
130
+ return true;
131
+ };
132
+
133
+ // Helper function to check bytes at specific offset
134
+ const hasAtOffset = (offset: number, bytes: number[]): boolean => {
135
+ if (buffer.length < offset + bytes.length) return false;
136
+ for (let i = 0; i < bytes.length; i++) {
137
+ if (buffer[offset + i] !== bytes[i]) return false;
138
+ }
139
+ return true;
140
+ };
141
+
142
+ // Images
143
+ if (startsWith([0xFF, 0xD8, 0xFF])) return "image/jpeg";
144
+ if (startsWith([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) return "image/png";
145
+ if (startsWith([0x47, 0x49, 0x46, 0x38])) return "image/gif";
146
+ if (startsWith([0x42, 0x4D])) return "image/bmp";
147
+ if (startsWith([0x49, 0x49, 0x2A, 0x00]) || startsWith([0x4D, 0x4D, 0x00, 0x2A])) return "image/tiff";
148
+ if (startsWith([0x00, 0x00, 0x01, 0x00])) return "image/x-icon";
149
+ if (buffer.toString("ascii", 0, 4) === "<svg" || buffer.toString("ascii", 0, 5) === "<?xml") {
150
+ if (buffer.toString("utf8").includes("<svg")) return "image/svg+xml";
151
+ }
152
+
153
+ // WebP and modern image formats
154
+ if (startsWith([0x52, 0x49, 0x46, 0x46]) && hasAtOffset(8, [0x57, 0x45, 0x42, 0x50])) return "image/webp";
155
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66])) return "image/avif";
156
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63])) return "image/heic";
157
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x6D, 0x69, 0x66, 0x31])) return "image/heif";
158
+
159
+ // Audio
160
+ if (startsWith([0xFF, 0xFB]) || startsWith([0xFF, 0xF3]) || startsWith([0xFF, 0xF2])) return "audio/mpeg";
161
+ if (startsWith([0x49, 0x44, 0x33])) return "audio/mpeg"; // MP3 with ID3
162
+ if (startsWith([0x52, 0x49, 0x46, 0x46]) && hasAtOffset(8, [0x57, 0x41, 0x56, 0x45])) return "audio/wav";
163
+ if (startsWith([0x4F, 0x67, 0x67, 0x53])) return "audio/ogg";
164
+ if (startsWith([0x66, 0x4C, 0x61, 0x43])) return "audio/flac";
165
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41])) return "audio/mp4";
166
+
167
+ // Video
168
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70])) {
169
+ const ftyp = buffer.toString("ascii", 8, 12);
170
+ if (ftyp.startsWith("mp4") || ftyp.startsWith("isom") || ftyp.startsWith("M4V")) return "video/mp4";
171
+ if (ftyp.startsWith("qt")) return "video/quicktime";
172
+ }
173
+ if (startsWith([0x1A, 0x45, 0xDF, 0xA3])) return "video/webm"; // Also mkv
174
+ if (startsWith([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11])) return "video/x-ms-wmv";
175
+ if (startsWith([0x46, 0x4C, 0x56, 0x01])) return "video/x-flv";
176
+ if (startsWith([0x52, 0x49, 0x46, 0x46]) && hasAtOffset(8, [0x41, 0x56, 0x49, 0x20])) return "video/x-msvideo";
177
+
178
+ // Documents
179
+ if (startsWith([0x25, 0x50, 0x44, 0x46])) return "application/pdf";
180
+ if (startsWith([0x50, 0x4B, 0x03, 0x04]) || startsWith([0x50, 0x4B, 0x05, 0x06]) || startsWith([0x50, 0x4B, 0x07, 0x08])) {
181
+ // ZIP-based formats - need to check internal structure
182
+ const content = buffer.toString("ascii", 30, 100);
183
+ if (content.includes("word/")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
184
+ if (content.includes("xl/")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
185
+ if (content.includes("ppt/")) return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
186
+ if (content.includes("META-INF/")) {
187
+ if (content.includes("content.xml")) return "application/vnd.oasis.opendocument.text";
188
+ }
189
+ return "application/zip";
190
+ }
191
+ if (startsWith([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
192
+ // Microsoft Office legacy formats
193
+ return "application/msword"; // Could also be Excel or PowerPoint
194
+ }
195
+ if (startsWith([0x7B, 0x5C, 0x72, 0x74, 0x66])) return "application/rtf";
196
+
197
+ // Archives
198
+ if (startsWith([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07])) return "application/vnd.rar";
199
+ if (startsWith([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) return "application/x-7z-compressed";
200
+ if (startsWith([0x1F, 0x8B])) return "application/gzip";
201
+ if (startsWith([0x42, 0x5A, 0x68])) return "application/x-bzip2";
202
+ if (startsWith([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) return "application/x-xz";
203
+ if (startsWith([0x75, 0x73, 0x74, 0x61, 0x72])) return "application/x-tar";
204
+
205
+ // Executables
206
+ if (startsWith([0x4D, 0x5A])) return "application/x-msdownload"; // Windows PE
207
+ if (startsWith([0x7F, 0x45, 0x4C, 0x46])) return "application/x-executable"; // Linux ELF
208
+ if (startsWith([0xCA, 0xFE, 0xBA, 0xBE]) || startsWith([0xFE, 0xED, 0xFA, 0xCE])) return "application/x-mach-binary"; // macOS binary
209
+ if (startsWith([0x50, 0x4B, 0x03, 0x04]) && buffer.toString("ascii", 30, 50).includes("AndroidManifest.xml")) return "application/vnd.android.package-archive";
210
+
211
+ // Fonts
212
+ if (startsWith([0x00, 0x01, 0x00, 0x00])) return "font/ttf";
213
+ if (startsWith([0x4F, 0x54, 0x54, 0x4F])) return "font/otf";
214
+ if (startsWith([0x77, 0x4F, 0x46, 0x46])) return "font/woff";
215
+ if (startsWith([0x77, 0x4F, 0x46, 0x32])) return "font/woff2";
216
+
217
+ // Web files (text-based, check for common patterns)
218
+ const textStart = buffer.toString("utf8", 0, Math.min(100, buffer.length)).toLowerCase();
219
+ if (textStart.includes("<!doctype html") || textStart.includes("<html")) return "text/html";
220
+ if (textStart.includes("<?xml")) return "application/xml";
221
+ if (textStart.startsWith("{") || textStart.startsWith("[")) {
222
+ try {
223
+ JSON.parse(buffer.toString("utf8"));
224
+ return "application/json";
225
+ } catch {
226
+ // Not JSON
227
+ }
228
+ }
229
+
230
+ // Other formats
231
+ if (startsWith([0x25, 0x21, 0x50, 0x53])) return "application/postscript";
232
+ if (startsWith([0x38, 0x42, 0x50, 0x53])) return "image/vnd.adobe.photoshop";
233
+ if (startsWith([0x49, 0x49, 0x2A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0x52])) return "image/x-canon-cr2";
234
+
235
+ return undefined;
236
+ }
@@ -109,10 +109,6 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
109
109
  if (!urlBase) {
110
110
  throw new Error("Missing URL");
111
111
  }
112
- if (urlBase === "/favicon.ico") {
113
- response.end();
114
- return;
115
- }
116
112
 
117
113
  let protocol = "https";
118
114
  let url = protocol + "://" + request.headers.host + request.url;