aboutsummaryrefslogtreecommitdiff
path: root/source/library/state/session-io.ts
diff options
context:
space:
mode:
Diffstat (limited to 'source/library/state/session-io.ts')
-rw-r--r--source/library/state/session-io.ts127
1 files changed, 127 insertions, 0 deletions
diff --git a/source/library/state/session-io.ts b/source/library/state/session-io.ts
new file mode 100644
index 0000000..a5e2ea9
--- /dev/null
+++ b/source/library/state/session-io.ts
@@ -0,0 +1,127 @@
+
+
+
+
+/*** UTILITY ------------------------------------------ ***/
+
+import type { Tab } from "./session.svelte.ts";
+
+const MAX_STRING_BYTES = 1024 * 1024;
+const MAX_TABS = 50;
+
+function isObject(value: unknown): value is Record<string, unknown> {
+ return typeof value === "object" && value !== null && !Array.isArray(value);
+}
+
+function stringTooLong(value: string): boolean {
+ return value.length > MAX_STRING_BYTES;
+}
+
+function validateTab(raw: unknown, index: number): TabExport | string {
+ if (!isObject(raw))
+ return `tabs[${index}]: not an object`;
+
+ if (typeof raw.headers !== "string")
+ return `tabs[${index}].headers: not a string`;
+
+ if (typeof raw.query !== "string")
+ return `tabs[${index}].query: not a string`;
+
+ if (typeof raw.title !== "string")
+ return `tabs[${index}].title: not a string`;
+
+ if (typeof raw.variables !== "string")
+ return `tabs[${index}].variables: not a string`;
+
+ if (raw.operationName !== null && typeof raw.operationName !== "string")
+ return `tabs[${index}].operationName: not a string or null`;
+
+ if (stringTooLong(raw.headers))
+ return `tabs[${index}].headers: exceeds 1 MB`;
+
+ if (stringTooLong(raw.query))
+ return `tabs[${index}].query: exceeds 1 MB`;
+
+ if (stringTooLong(raw.title))
+ return `tabs[${index}].title: exceeds 1 MB`;
+
+ if (stringTooLong(raw.variables))
+ return `tabs[${index}].variables: exceeds 1 MB`;
+
+ if (typeof raw.operationName === "string" && stringTooLong(raw.operationName))
+ return `tabs[${index}].operationName: exceeds 1 MB`;
+
+ return {
+ headers: raw.headers,
+ operationName: raw.operationName,
+ query: raw.query,
+ title: raw.title,
+ variables: raw.variables
+ };
+}
+
+/*** EXPORT ------------------------------------------- ***/
+
+export type TabExport = {
+ headers: string;
+ operationName: string | null;
+ query: string;
+ title: string;
+ variables: string;
+};
+
+export type SessionExport = {
+ exportedAt: string;
+ tabs: TabExport[];
+ version: 1;
+};
+
+export type ImportResult = {
+ added: number;
+ errors: string[];
+ skipped: number;
+};
+
+export function tabToExport(tab: Tab): TabExport {
+ return {
+ headers: tab.headers,
+ operationName: tab.operationName,
+ query: tab.query,
+ title: tab.title,
+ variables: tab.variables
+ };
+}
+
+export function validateSessionExport(data: unknown): SessionExport | { error: string } {
+ if (!isObject(data))
+ return { error: "not an object" };
+
+ if (data.version !== 1)
+ return { error: `unsupported version: ${String(data.version)}` };
+
+ if (typeof data.exportedAt !== "string")
+ return { error: "exportedAt: not a string" };
+
+ if (!Array.isArray(data.tabs))
+ return { error: "tabs: not an array" };
+
+ if (data.tabs.length > MAX_TABS)
+ return { error: `tabs: exceeds ${MAX_TABS}` };
+
+ const tabs: TabExport[] = [];
+
+ for (let i = 0; i < data.tabs.length; i++) {
+ const result = validateTab(data.tabs[i], i);
+
+ if (typeof result === "string")
+ return { error: result };
+
+ tabs.push(result);
+ }
+
+ return {
+ exportedAt: data.exportedAt,
+ tabs,
+ version: 1
+ };
+}