1 | |
2 |
|
3 | const percentEncode = (s: string) => |
4 | encodeURIComponent(s).replace( |
5 | /[!'()*]/g, |
6 | (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase() |
7 | ) |
8 |
|
9 | function restBase(auth: RT.Netsuite) { |
10 | return `https://${auth.account_id.trim().toLowerCase()}.suitetalk.api.netsuite.com/services/rest` |
11 | } |
12 |
|
13 | |
14 | async function authHeader(auth: RT.Netsuite, method: string, url: URL) { |
15 | if (auth.token !== undefined && auth.token !== "") { |
16 | return `Bearer ${auth.token}` |
17 | } |
18 | |
19 | |
20 | url.search = [...url.searchParams.entries()] |
21 | .map(([k, v]) => `${percentEncode(k)}=${percentEncode(v)}`) |
22 | .join("&") |
23 | const nonce = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => |
24 | b.toString(16).padStart(2, "0") |
25 | ).join("") |
26 | const timestamp = Math.floor(Date.now() / 1000).toString() |
27 | const oauthParams: [string, string][] = [ |
28 | ["oauth_consumer_key", auth.consumer_key ?? ""], |
29 | ["oauth_nonce", nonce], |
30 | ["oauth_signature_method", "HMAC-SHA256"], |
31 | ["oauth_timestamp", timestamp], |
32 | ["oauth_token", auth.token_id ?? ""], |
33 | ["oauth_version", "1.0"], |
34 | ] |
35 | const sortedParams = [...url.searchParams.entries(), ...oauthParams] |
36 | .map(([k, v]) => [percentEncode(k), percentEncode(v)]) |
37 | .sort(([ak, av], [bk, bv]) => |
38 | ak === bk ? (av < bv ? -1 : 1) : ak < bk ? -1 : 1 |
39 | ) |
40 | .map(([k, v]) => `${k}=${v}`) |
41 | .join("&") |
42 | const baseString = [ |
43 | method.toUpperCase(), |
44 | percentEncode(`${url.protocol}//${url.host}${url.pathname}`), |
45 | percentEncode(sortedParams), |
46 | ].join("&") |
47 | const key = await crypto.subtle.importKey( |
48 | "raw", |
49 | new TextEncoder().encode( |
50 | `${percentEncode(auth.consumer_secret ?? "")}&${percentEncode(auth.token_secret ?? "")}` |
51 | ), |
52 | { name: "HMAC", hash: "SHA-256" }, |
53 | false, |
54 | ["sign"] |
55 | ) |
56 | const signature = btoa( |
57 | String.fromCharCode( |
58 | ...new Uint8Array( |
59 | await crypto.subtle.sign( |
60 | "HMAC", |
61 | key, |
62 | new TextEncoder().encode(baseString) |
63 | ) |
64 | ) |
65 | ) |
66 | ) |
67 | const realm = auth.account_id.trim().toUpperCase().replace(/-/g, "_") |
68 | return `OAuth realm="${realm}", oauth_consumer_key="${percentEncode(auth.consumer_key ?? "")}", oauth_token="${percentEncode(auth.token_id ?? "")}", oauth_signature_method="HMAC-SHA256", oauth_timestamp="${timestamp}", oauth_nonce="${nonce}", oauth_version="1.0", oauth_signature="${percentEncode(signature)}"` |
69 | } |
70 |
|
71 | export type DynSelect_record_type = string |
72 |
|
73 | |
74 | export async function record_type(auth: RT.Netsuite) { |
75 | const url = new URL(`${restBase(auth)}/record/v1/metadata-catalog`) |
76 | const response = await fetch(url, { |
77 | headers: { |
78 | Authorization: await authHeader(auth, "GET", url), |
79 | Accept: "application/json", |
80 | }, |
81 | }) |
82 | if (!response.ok) { |
83 | throw new Error(`${response.status} ${await response.text()}`) |
84 | } |
85 | const { items } = (await response.json()) as { items: { name: string }[] } |
86 | return items.map((i) => ({ value: i.name, label: i.name })) |
87 | } |
88 |
|
89 | |
90 | * Delete Record |
91 | * Delete a record by internal ID. |
92 | */ |
93 | export async function main( |
94 | auth: RT.Netsuite, |
95 | record_type: DynSelect_record_type, |
96 | id: string |
97 | ) { |
98 | const url = new URL(`${restBase(auth)}/record/v1/${record_type}/${id}`) |
99 | const response = await fetch(url, { |
100 | method: "DELETE", |
101 | headers: { |
102 | Authorization: await authHeader(auth, "DELETE", url), |
103 | Accept: "application/json", |
104 | }, |
105 | }) |
106 | if (!response.ok) { |
107 | throw new Error(`${response.status} ${await response.text()}`) |
108 | } |
109 | if (response.status === 204) { |
110 | return { success: true, id } |
111 | } |
112 | return await response.json() |
113 | } |
114 |
|