@vicket/create-support 1.1.1
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/README.md +52 -0
- package/bin/create-vicket-support.js +389 -0
- package/package.json +18 -0
- package/templates/next/src/app/api/vicket/[...path]/route.ts +59 -0
- package/templates/next/src/app/components/vicket/TicketDialog.tsx +514 -0
- package/templates/next/src/app/support/page.tsx +358 -0
- package/templates/next/src/app/ticket/page.tsx +483 -0
- package/templates/next/src/app/utils/vicket/api.ts +149 -0
- package/templates/next/src/app/utils/vicket/types.ts +85 -0
- package/templates/next/src/app/utils/vicket/utils.ts +49 -0
- package/templates/next/src/app/vicket.css +1325 -0
- package/templates/nuxt/app/assets/css/vicket.css +1325 -0
- package/templates/nuxt/app/components/VicketTicketDialog.vue +499 -0
- package/templates/nuxt/app/composables/useVicket.ts +274 -0
- package/templates/nuxt/app/pages/support.vue +303 -0
- package/templates/nuxt/app/pages/ticket.vue +434 -0
- package/templates/nuxt/server/api/vicket/[...path].ts +85 -0
- package/templates/sveltekit/src/lib/vicket/TicketDialog.svelte +459 -0
- package/templates/sveltekit/src/lib/vicket/api.ts +162 -0
- package/templates/sveltekit/src/lib/vicket/types.ts +87 -0
- package/templates/sveltekit/src/lib/vicket/utils.ts +55 -0
- package/templates/sveltekit/src/lib/vicket.css +1325 -0
- package/templates/sveltekit/src/routes/api/vicket/[...path]/+server.ts +77 -0
- package/templates/sveltekit/src/routes/support/+page.svelte +316 -0
- package/templates/sveltekit/src/routes/ticket/+page.svelte +418 -0
- package/templates-tailwind/next/src/app/api/vicket/init/route.ts +24 -0
- package/templates-tailwind/next/src/app/api/vicket/messages/route.ts +36 -0
- package/templates-tailwind/next/src/app/api/vicket/thread/route.ts +27 -0
- package/templates-tailwind/next/src/app/api/vicket/tickets/route.ts +37 -0
- package/templates-tailwind/next/src/app/support/page.tsx +5 -0
- package/templates-tailwind/next/src/app/ticket/page.tsx +10 -0
- package/templates-tailwind/next/src/components/vicket/support-page.tsx +359 -0
- package/templates-tailwind/next/src/components/vicket/ticket-dialog.tsx +306 -0
- package/templates-tailwind/next/src/components/vicket/ticket-page.tsx +425 -0
- package/templates-tailwind/next/src/lib/vicket.ts +257 -0
- package/templates-tailwind/nuxt/app/components/VicketSupportPage.vue +317 -0
- package/templates-tailwind/nuxt/app/components/VicketTicketDialog.vue +444 -0
- package/templates-tailwind/nuxt/app/components/VicketTicketPage.vue +449 -0
- package/templates-tailwind/nuxt/app/composables/use-vicket.ts +249 -0
- package/templates-tailwind/nuxt/app/pages/support.vue +3 -0
- package/templates-tailwind/nuxt/app/pages/ticket.vue +3 -0
- package/templates-tailwind/nuxt/server/api/vicket/init.get.ts +22 -0
- package/templates-tailwind/nuxt/server/api/vicket/messages.post.ts +56 -0
- package/templates-tailwind/nuxt/server/api/vicket/thread.get.ts +26 -0
- package/templates-tailwind/nuxt/server/api/vicket/tickets.post.ts +53 -0
- package/templates-tailwind/sveltekit/src/lib/vicket/SupportPage.svelte +395 -0
- package/templates-tailwind/sveltekit/src/lib/vicket/TicketDialog.svelte +406 -0
- package/templates-tailwind/sveltekit/src/lib/vicket/TicketPage.svelte +465 -0
- package/templates-tailwind/sveltekit/src/lib/vicket/index.ts +257 -0
- package/templates-tailwind/sveltekit/src/routes/api/vicket/init/+server.ts +22 -0
- package/templates-tailwind/sveltekit/src/routes/api/vicket/messages/+server.ts +40 -0
- package/templates-tailwind/sveltekit/src/routes/api/vicket/thread/+server.ts +25 -0
- package/templates-tailwind/sveltekit/src/routes/api/vicket/tickets/+server.ts +37 -0
- package/templates-tailwind/sveltekit/src/routes/support/+page.svelte +5 -0
- package/templates-tailwind/sveltekit/src/routes/ticket/+page.svelte +5 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* ── Types ─────────────────────────────────────── */
|
|
2
|
+
|
|
3
|
+
export type TemplateOption = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type TemplateQuestion = {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
type: "TEXT" | "TEXTAREA" | "SELECT" | "CHECKBOX" | "DATE" | "FILE";
|
|
13
|
+
required: boolean;
|
|
14
|
+
order: number;
|
|
15
|
+
options?: TemplateOption[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type Template = {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
questions: TemplateQuestion[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type Article = {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
content: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type Faq = {
|
|
33
|
+
id: string;
|
|
34
|
+
question: string;
|
|
35
|
+
answer: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type SupportInitResponse = {
|
|
39
|
+
success: boolean;
|
|
40
|
+
data?: {
|
|
41
|
+
website?: { name?: string };
|
|
42
|
+
templates: Template[];
|
|
43
|
+
articles?: Article[];
|
|
44
|
+
faqs?: Faq[];
|
|
45
|
+
};
|
|
46
|
+
error?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type FormValues = {
|
|
50
|
+
email: string;
|
|
51
|
+
title: string;
|
|
52
|
+
answers: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type Attachment = {
|
|
56
|
+
id: string;
|
|
57
|
+
original_filename: string;
|
|
58
|
+
url: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type Message = {
|
|
62
|
+
id: string;
|
|
63
|
+
content: string;
|
|
64
|
+
author_type: "reporter" | "user" | "system";
|
|
65
|
+
created_at: string;
|
|
66
|
+
attachments?: Attachment[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type TicketAnswer = {
|
|
70
|
+
id: string;
|
|
71
|
+
question_label: string;
|
|
72
|
+
answer: string;
|
|
73
|
+
attachments?: Attachment[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type TicketThread = {
|
|
77
|
+
id: string;
|
|
78
|
+
title: string;
|
|
79
|
+
status?: { label: string };
|
|
80
|
+
priority?: { label: string };
|
|
81
|
+
messages: Message[];
|
|
82
|
+
answers?: TicketAnswer[];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/* ── Constants ─────────────────────────────────── */
|
|
86
|
+
|
|
87
|
+
const PROXY_BASE = "/api/vicket";
|
|
88
|
+
|
|
89
|
+
export const AUTHOR_LABELS: Record<string, string> = {
|
|
90
|
+
reporter: "You",
|
|
91
|
+
user: "Support",
|
|
92
|
+
system: "System",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const initialFormValues: FormValues = { email: "", title: "", answers: {} };
|
|
96
|
+
|
|
97
|
+
/* ── Helpers ───────────────────────────────────── */
|
|
98
|
+
|
|
99
|
+
export function cn(...classes: (string | false | null | undefined)[]): string {
|
|
100
|
+
return classes.filter(Boolean).join(" ");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function stripHtml(html: string): string {
|
|
104
|
+
return html.replace(/<[^>]*>/g, "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function sanitizeHtml(html: string): string {
|
|
108
|
+
return html
|
|
109
|
+
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
|
110
|
+
.replace(/\son\w+="[^"]*"/gi, "")
|
|
111
|
+
.replace(/\son\w+='[^']*'/gi, "")
|
|
112
|
+
.replace(/href\s*=\s*"javascript:[^"]*"/gi, 'href="#"')
|
|
113
|
+
.replace(/href\s*=\s*'javascript:[^']*'/gi, "href='#'");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatDate(iso: string): string {
|
|
117
|
+
try {
|
|
118
|
+
return new Intl.DateTimeFormat("en", {
|
|
119
|
+
month: "short",
|
|
120
|
+
day: "numeric",
|
|
121
|
+
hour: "numeric",
|
|
122
|
+
minute: "2-digit",
|
|
123
|
+
}).format(new Date(iso));
|
|
124
|
+
} catch {
|
|
125
|
+
return iso;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isFileAnswer(answer: string): boolean {
|
|
130
|
+
return answer?.includes("__isFile:true") || answer?.includes("map[__isFile");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function formatAnswerText(value: string): string {
|
|
134
|
+
if (!value) return "";
|
|
135
|
+
const trimmed = value.trim();
|
|
136
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
137
|
+
const rawItems = trimmed.slice(1, -1).trim();
|
|
138
|
+
return rawItems.length > 0 ? rawItems.split(/\s+/).join(", ") : "";
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ── API (uses Nuxt $fetch) ────────────────────── */
|
|
144
|
+
|
|
145
|
+
export async function fetchInit(): Promise<NonNullable<SupportInitResponse["data"]>> {
|
|
146
|
+
const payload = await $fetch<SupportInitResponse>(`${PROXY_BASE}/init`, { method: "GET" });
|
|
147
|
+
if (!payload?.success || !payload?.data) {
|
|
148
|
+
throw new Error(payload?.error || "Failed to load support data.");
|
|
149
|
+
}
|
|
150
|
+
return payload.data;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function createTicket(body: {
|
|
154
|
+
email: string;
|
|
155
|
+
title: string;
|
|
156
|
+
templateId: string;
|
|
157
|
+
answers: Record<string, unknown>;
|
|
158
|
+
hasFiles: boolean;
|
|
159
|
+
fileQuestionIds: string[];
|
|
160
|
+
}): Promise<{ emailLimitReached?: boolean; warning?: string }> {
|
|
161
|
+
const payload = {
|
|
162
|
+
email: body.email,
|
|
163
|
+
title: body.title,
|
|
164
|
+
templateId: body.templateId,
|
|
165
|
+
answers: { ...body.answers },
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
let response: Response;
|
|
169
|
+
if (body.hasFiles) {
|
|
170
|
+
const formData = new FormData();
|
|
171
|
+
const normalizedAnswers: Record<string, unknown> = {};
|
|
172
|
+
for (const [questionId, answer] of Object.entries(payload.answers)) {
|
|
173
|
+
if (answer instanceof File) {
|
|
174
|
+
formData.append(`files[${questionId}]`, answer);
|
|
175
|
+
normalizedAnswers[questionId] = "__isFile:true";
|
|
176
|
+
} else {
|
|
177
|
+
normalizedAnswers[questionId] = answer;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
formData.append("data", JSON.stringify({ ...payload, answers: normalizedAnswers }));
|
|
181
|
+
response = await fetch(`${PROXY_BASE}/tickets`, { method: "POST", body: formData });
|
|
182
|
+
} else {
|
|
183
|
+
response = await fetch(`${PROXY_BASE}/tickets`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: { "Content-Type": "application/json" },
|
|
186
|
+
body: JSON.stringify(payload),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const responsePayload = (await response.json()) as {
|
|
191
|
+
error?: string;
|
|
192
|
+
success?: boolean;
|
|
193
|
+
data?: { email_limit_reached?: boolean; warning?: string };
|
|
194
|
+
};
|
|
195
|
+
if (!response.ok || !responsePayload?.success) {
|
|
196
|
+
throw new Error(responsePayload?.error || "Failed to create ticket.");
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
emailLimitReached: responsePayload.data?.email_limit_reached ?? false,
|
|
200
|
+
warning: responsePayload.data?.warning,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function fetchThread(token: string): Promise<TicketThread> {
|
|
205
|
+
const payload = await $fetch<{
|
|
206
|
+
success?: boolean;
|
|
207
|
+
error?: string;
|
|
208
|
+
error_code?: string;
|
|
209
|
+
data?: TicketThread;
|
|
210
|
+
}>(`${PROXY_BASE}/thread?token=${encodeURIComponent(token)}`, { method: "GET" });
|
|
211
|
+
|
|
212
|
+
if (!payload?.success || !payload?.data) {
|
|
213
|
+
if (payload?.error_code === "ticket-link-expired") {
|
|
214
|
+
throw new Error("This link has expired. A new secure link has been sent to your email.");
|
|
215
|
+
}
|
|
216
|
+
throw new Error(payload?.error || "Failed to load ticket.");
|
|
217
|
+
}
|
|
218
|
+
return payload.data;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function sendReply(token: string, content: string, files: File[]): Promise<void> {
|
|
222
|
+
const url = `${PROXY_BASE}/messages?token=${encodeURIComponent(token)}`;
|
|
223
|
+
|
|
224
|
+
let response: Response;
|
|
225
|
+
if (files.length > 0) {
|
|
226
|
+
const formData = new FormData();
|
|
227
|
+
formData.append("data", JSON.stringify({ content }));
|
|
228
|
+
for (const file of files) formData.append("files", file);
|
|
229
|
+
response = await fetch(url, { method: "POST", body: formData });
|
|
230
|
+
} else {
|
|
231
|
+
response = await fetch(url, {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers: { "Content-Type": "application/json" },
|
|
234
|
+
body: JSON.stringify({ content }),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const responsePayload = (await response.json()) as {
|
|
239
|
+
success?: boolean;
|
|
240
|
+
error?: string;
|
|
241
|
+
error_code?: string;
|
|
242
|
+
};
|
|
243
|
+
if (!response.ok || !responsePayload?.success) {
|
|
244
|
+
if (responsePayload?.error_code === "ticket-link-expired") {
|
|
245
|
+
throw new Error("This link has expired. A new secure link has been sent to your email.");
|
|
246
|
+
}
|
|
247
|
+
throw new Error(responsePayload?.error || "Failed to send reply.");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
|
|
5
|
+
const apiKey = process.env.VICKET_API_KEY || "";
|
|
6
|
+
|
|
7
|
+
if (!apiUrl || !apiKey) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 500,
|
|
10
|
+
statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const response = await fetch(`${apiUrl}/public/support/init`, {
|
|
15
|
+
method: "GET",
|
|
16
|
+
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
event.node.res.statusCode = response.status;
|
|
21
|
+
return data;
|
|
22
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { defineEventHandler, getQuery, readBody, readMultipartFormData } from "h3";
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
|
|
5
|
+
const apiKey = process.env.VICKET_API_KEY || "";
|
|
6
|
+
|
|
7
|
+
if (!apiUrl || !apiKey) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 500,
|
|
10
|
+
statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const query = getQuery(event);
|
|
15
|
+
const token = String(query.token || "");
|
|
16
|
+
const targetUrl = `${apiUrl}/public/support/ticket/messages?token=${encodeURIComponent(token)}`;
|
|
17
|
+
|
|
18
|
+
const contentType = event.node.req.headers["content-type"] || "";
|
|
19
|
+
const isMultipart = contentType.includes("multipart/form-data");
|
|
20
|
+
|
|
21
|
+
let fetchOptions: RequestInit;
|
|
22
|
+
|
|
23
|
+
if (isMultipart) {
|
|
24
|
+
const parts = await readMultipartFormData(event);
|
|
25
|
+
const formData = new FormData();
|
|
26
|
+
|
|
27
|
+
if (parts) {
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (part.filename) {
|
|
30
|
+
const blob = new Blob([part.data], { type: part.type || "application/octet-stream" });
|
|
31
|
+
formData.append(part.name || "file", blob, part.filename);
|
|
32
|
+
} else {
|
|
33
|
+
formData.append(part.name || "field", part.data.toString("utf-8"));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fetchOptions = {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "X-Api-Key": apiKey },
|
|
41
|
+
body: formData,
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
const body = await readBody(event);
|
|
45
|
+
fetchOptions = {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
|
|
48
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const response = await fetch(targetUrl, fetchOptions);
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
event.node.res.statusCode = response.status;
|
|
55
|
+
return data;
|
|
56
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineEventHandler, getQuery } from "h3";
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
|
|
5
|
+
const apiKey = process.env.VICKET_API_KEY || "";
|
|
6
|
+
|
|
7
|
+
if (!apiUrl || !apiKey) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 500,
|
|
10
|
+
statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const query = getQuery(event);
|
|
15
|
+
const token = String(query.token || "");
|
|
16
|
+
const targetUrl = `${apiUrl}/public/support/ticket?token=${encodeURIComponent(token)}`;
|
|
17
|
+
|
|
18
|
+
const response = await fetch(targetUrl, {
|
|
19
|
+
method: "GET",
|
|
20
|
+
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
event.node.res.statusCode = response.status;
|
|
25
|
+
return data;
|
|
26
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, readMultipartFormData } from "h3";
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const apiUrl = (process.env.VICKET_API_URL || "").replace(/\/+$/, "");
|
|
5
|
+
const apiKey = process.env.VICKET_API_KEY || "";
|
|
6
|
+
|
|
7
|
+
if (!apiUrl || !apiKey) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 500,
|
|
10
|
+
statusMessage: "Missing VICKET_API_URL or VICKET_API_KEY environment variable.",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const contentType = event.node.req.headers["content-type"] || "";
|
|
15
|
+
const isMultipart = contentType.includes("multipart/form-data");
|
|
16
|
+
const targetUrl = `${apiUrl}/public/support/tickets`;
|
|
17
|
+
|
|
18
|
+
let fetchOptions: RequestInit;
|
|
19
|
+
|
|
20
|
+
if (isMultipart) {
|
|
21
|
+
const parts = await readMultipartFormData(event);
|
|
22
|
+
const formData = new FormData();
|
|
23
|
+
|
|
24
|
+
if (parts) {
|
|
25
|
+
for (const part of parts) {
|
|
26
|
+
if (part.filename) {
|
|
27
|
+
const blob = new Blob([part.data], { type: part.type || "application/octet-stream" });
|
|
28
|
+
formData.append(part.name || "file", blob, part.filename);
|
|
29
|
+
} else {
|
|
30
|
+
formData.append(part.name || "field", part.data.toString("utf-8"));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fetchOptions = {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "X-Api-Key": apiKey },
|
|
38
|
+
body: formData,
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
const body = await readBody(event);
|
|
42
|
+
fetchOptions = {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
|
|
45
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const response = await fetch(targetUrl, fetchOptions);
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
event.node.res.statusCode = response.status;
|
|
52
|
+
return data;
|
|
53
|
+
});
|