aboutsummaryrefslogtreecommitdiff
path: root/source/library/state/session-io.ts
blob: a5e2ea9d647b687285105c9fddf4d52f9930be66 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
  };
}