vicket 0.1.0 → 0.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.
@@ -0,0 +1,101 @@
1
+ type TemplateOption = {
2
+ id: string;
3
+ label: string;
4
+ value: string;
5
+ };
6
+ type TemplateQuestion = {
7
+ id: string;
8
+ label: string;
9
+ type: "TEXT" | "TEXTAREA" | "SELECT" | "CHECKBOX" | "DATE" | "FILE";
10
+ required: boolean;
11
+ order: number;
12
+ options?: TemplateOption[];
13
+ };
14
+ type Template = {
15
+ id: string;
16
+ name: string;
17
+ description: string;
18
+ questions: TemplateQuestion[];
19
+ };
20
+ type Article = {
21
+ id: string;
22
+ title: string;
23
+ slug: string;
24
+ content: string;
25
+ };
26
+ type Faq = {
27
+ id: string;
28
+ question: string;
29
+ answer: string;
30
+ };
31
+ type SupportInitResponse = {
32
+ success: boolean;
33
+ data?: {
34
+ website?: {
35
+ name?: string;
36
+ };
37
+ templates: Template[];
38
+ articles?: Article[];
39
+ faqs?: Faq[];
40
+ };
41
+ error?: string;
42
+ };
43
+ type FormValues = {
44
+ email: string;
45
+ title: string;
46
+ answers: Record<string, unknown>;
47
+ };
48
+ type Attachment = {
49
+ id: string;
50
+ original_filename: string;
51
+ url: string;
52
+ };
53
+ type Message = {
54
+ id: string;
55
+ content: string;
56
+ author_type: "reporter" | "user" | "system";
57
+ created_at: string;
58
+ attachments?: Attachment[];
59
+ };
60
+ type TicketAnswer = {
61
+ id: string;
62
+ question_label: string;
63
+ answer: string;
64
+ attachments?: Attachment[];
65
+ };
66
+ type TicketThread = {
67
+ id: string;
68
+ title: string;
69
+ status?: {
70
+ label: string;
71
+ };
72
+ priority?: {
73
+ label: string;
74
+ };
75
+ messages: Message[];
76
+ answers?: TicketAnswer[];
77
+ };
78
+ declare const AUTHOR_LABELS: Record<string, string>;
79
+ declare const initialFormValues: FormValues;
80
+ declare function cn(...classes: (string | boolean | undefined | null)[]): string;
81
+ declare function stripHtml(html: string): string;
82
+ declare function sanitizeHtml(html: string): string;
83
+ declare function formatDate(iso: string): string;
84
+ declare function isFileAnswer(answer: string): boolean;
85
+ declare function formatAnswerText(value: string): string;
86
+ declare function fetchInit(): Promise<NonNullable<SupportInitResponse["data"]>>;
87
+ declare function createTicket(body: {
88
+ email: string;
89
+ title: string;
90
+ templateId: string;
91
+ answers: Record<string, unknown>;
92
+ hasFiles: boolean;
93
+ fileQuestionIds: string[];
94
+ }): Promise<{
95
+ emailLimitReached?: boolean;
96
+ warning?: string;
97
+ }>;
98
+ declare function fetchThread(token: string): Promise<TicketThread>;
99
+ declare function sendReply(token: string, content: string, files: File[]): Promise<void>;
100
+
101
+ export { AUTHOR_LABELS, type Article, type Attachment, type Faq, type FormValues, type Message, type SupportInitResponse, type Template, type TemplateOption, type TemplateQuestion, type TicketAnswer, type TicketThread, cn, createTicket, fetchInit, fetchThread, formatAnswerText, formatDate, initialFormValues, isFileAnswer, sanitizeHtml, sendReply, stripHtml };
package/dist/index.js ADDED
@@ -0,0 +1,141 @@
1
+ // src/index.ts
2
+ var PROXY_BASE = "/api/vicket";
3
+ var AUTHOR_LABELS = {
4
+ reporter: "You",
5
+ user: "Support",
6
+ system: "System"
7
+ };
8
+ var initialFormValues = { email: "", title: "", answers: {} };
9
+ function cn(...classes) {
10
+ return classes.filter(Boolean).join(" ");
11
+ }
12
+ function stripHtml(html) {
13
+ return html.replace(/<[^>]*>/g, "");
14
+ }
15
+ function sanitizeHtml(html) {
16
+ return html.replace(/<script[\s>][\s\S]*?<\/script>/gi, "").replace(/<iframe[\s>][\s\S]*?<\/iframe>/gi, "").replace(/on\w+\s*=\s*"[^"]*"/gi, "").replace(/on\w+\s*=\s*'[^']*'/gi, "").replace(/on\w+\s*=\s*[^\s>]*/gi, "").replace(/javascript\s*:/gi, "");
17
+ }
18
+ function formatDate(iso) {
19
+ try {
20
+ return new Intl.DateTimeFormat("en", {
21
+ month: "short",
22
+ day: "numeric",
23
+ hour: "numeric",
24
+ minute: "2-digit"
25
+ }).format(new Date(iso));
26
+ } catch {
27
+ return iso;
28
+ }
29
+ }
30
+ function isFileAnswer(answer) {
31
+ return answer?.includes("__isFile:true") || answer?.includes("map[__isFile");
32
+ }
33
+ function formatAnswerText(value) {
34
+ if (!value) return "";
35
+ const trimmed = value.trim();
36
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
37
+ const rawItems = trimmed.slice(1, -1).trim();
38
+ return rawItems.length > 0 ? rawItems.split(/\s+/).join(", ") : "";
39
+ }
40
+ return value;
41
+ }
42
+ async function fetchInit() {
43
+ const response = await fetch(`${PROXY_BASE}/init`, {
44
+ method: "GET",
45
+ cache: "no-store",
46
+ headers: { "Content-Type": "application/json" }
47
+ });
48
+ const payload = await response.json();
49
+ if (!response.ok || !payload?.success || !payload?.data) {
50
+ throw new Error(payload?.error || "Failed to load support data.");
51
+ }
52
+ return payload.data;
53
+ }
54
+ async function createTicket(body) {
55
+ const payload = {
56
+ email: body.email,
57
+ title: body.title,
58
+ templateId: body.templateId,
59
+ answers: { ...body.answers }
60
+ };
61
+ let response;
62
+ if (body.hasFiles) {
63
+ const formData = new FormData();
64
+ const normalizedAnswers = {};
65
+ for (const [questionId, answer] of Object.entries(payload.answers)) {
66
+ if (answer instanceof File) {
67
+ formData.append(`files[${questionId}]`, answer);
68
+ normalizedAnswers[questionId] = "__isFile:true";
69
+ } else {
70
+ normalizedAnswers[questionId] = answer;
71
+ }
72
+ }
73
+ formData.append("data", JSON.stringify({ ...payload, answers: normalizedAnswers }));
74
+ response = await fetch(`${PROXY_BASE}/tickets`, { method: "POST", body: formData });
75
+ } else {
76
+ response = await fetch(`${PROXY_BASE}/tickets`, {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify(payload)
80
+ });
81
+ }
82
+ const responsePayload = await response.json();
83
+ if (!response.ok || !responsePayload?.success) {
84
+ throw new Error(responsePayload?.error || "Failed to create ticket.");
85
+ }
86
+ return {
87
+ emailLimitReached: responsePayload.data?.email_limit_reached ?? false,
88
+ warning: responsePayload.data?.warning
89
+ };
90
+ }
91
+ async function fetchThread(token) {
92
+ const response = await fetch(
93
+ `${PROXY_BASE}/ticket?token=${encodeURIComponent(token)}`,
94
+ { method: "GET", cache: "no-store", headers: { "Content-Type": "application/json" } }
95
+ );
96
+ const payload = await response.json();
97
+ if (!response.ok || !payload?.success || !payload?.data) {
98
+ if (payload?.error_code === "ticket-link-expired") {
99
+ throw new Error("This link has expired. A new secure link has been sent to your email.");
100
+ }
101
+ throw new Error(payload?.error || "Failed to load ticket.");
102
+ }
103
+ return payload.data;
104
+ }
105
+ async function sendReply(token, content, files) {
106
+ const url = `${PROXY_BASE}/ticket/messages?token=${encodeURIComponent(token)}`;
107
+ let response;
108
+ if (files.length > 0) {
109
+ const formData = new FormData();
110
+ formData.append("data", JSON.stringify({ content }));
111
+ for (const file of files) formData.append("files", file);
112
+ response = await fetch(url, { method: "POST", body: formData });
113
+ } else {
114
+ response = await fetch(url, {
115
+ method: "POST",
116
+ headers: { "Content-Type": "application/json" },
117
+ body: JSON.stringify({ content })
118
+ });
119
+ }
120
+ const payload = await response.json();
121
+ if (!response.ok || !payload?.success) {
122
+ if (payload?.error_code === "ticket-link-expired") {
123
+ throw new Error("This link has expired. A new secure link has been sent to your email.");
124
+ }
125
+ throw new Error(payload?.error || "Failed to send reply.");
126
+ }
127
+ }
128
+ export {
129
+ AUTHOR_LABELS,
130
+ cn,
131
+ createTicket,
132
+ fetchInit,
133
+ fetchThread,
134
+ formatAnswerText,
135
+ formatDate,
136
+ initialFormValues,
137
+ isFileAnswer,
138
+ sanitizeHtml,
139
+ sendReply,
140
+ stripHtml
141
+ };
package/dist/next.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ declare function createVicketProxy(options?: {
2
+ apiUrl?: string;
3
+ apiKey?: string;
4
+ }): (req: Request, context: {
5
+ params: Promise<{
6
+ path: string[];
7
+ }>;
8
+ }) => Promise<Response>;
9
+
10
+ export { createVicketProxy };
package/dist/next.js ADDED
@@ -0,0 +1,39 @@
1
+ // src/next.ts
2
+ function createVicketProxy(options) {
3
+ return async function proxy(req, context) {
4
+ const apiUrl = (options?.apiUrl || process.env.VICKET_API_URL || "").replace(/\/+$/, "");
5
+ const apiKey = options?.apiKey || process.env.VICKET_API_KEY || "";
6
+ if (!apiUrl || !apiKey) {
7
+ return Response.json(
8
+ { error: "Missing VICKET_API_URL or VICKET_API_KEY server environment variables." },
9
+ { status: 500 }
10
+ );
11
+ }
12
+ const { path } = await context.params;
13
+ const subpath = path.join("/");
14
+ const url = new URL(`${apiUrl}/public/support/${subpath}`);
15
+ const reqUrl = new URL(req.url);
16
+ reqUrl.searchParams.forEach((value, key) => url.searchParams.set(key, value));
17
+ const headers = { "X-Api-Key": apiKey };
18
+ const contentType = req.headers.get("content-type") || "";
19
+ let body = null;
20
+ if (req.method !== "GET" && req.method !== "HEAD") {
21
+ if (contentType.includes("multipart/form-data")) {
22
+ body = await req.blob();
23
+ headers["Content-Type"] = contentType;
24
+ } else {
25
+ body = await req.text();
26
+ headers["Content-Type"] = contentType || "application/json";
27
+ }
28
+ }
29
+ const upstream = await fetch(url.toString(), { method: req.method, headers, body });
30
+ const responseBody = await upstream.arrayBuffer();
31
+ return new Response(responseBody, {
32
+ status: upstream.status,
33
+ headers: { "Content-Type": upstream.headers.get("Content-Type") || "application/json" }
34
+ });
35
+ };
36
+ }
37
+ export {
38
+ createVicketProxy
39
+ };
@@ -0,0 +1,11 @@
1
+ import { SupportInitResponse, TicketThread } from './index.js';
2
+
3
+ declare function createServerClient(options?: {
4
+ apiUrl?: string;
5
+ apiKey?: string;
6
+ }): {
7
+ fetchInit: () => Promise<NonNullable<SupportInitResponse["data"]>>;
8
+ fetchThread: (token: string) => Promise<TicketThread>;
9
+ };
10
+
11
+ export { createServerClient };
package/dist/server.js ADDED
@@ -0,0 +1,41 @@
1
+ // src/server.ts
2
+ function createServerClient(options) {
3
+ const apiUrl = (options?.apiUrl || process.env.VICKET_API_URL || "").replace(/\/+$/, "");
4
+ const apiKey = options?.apiKey || process.env.VICKET_API_KEY || "";
5
+ async function fetchInit() {
6
+ if (!apiUrl || !apiKey) throw new Error("Missing VICKET_API_URL or VICKET_API_KEY.");
7
+ const res = await fetch(`${apiUrl}/public/support/init`, {
8
+ method: "GET",
9
+ cache: "no-store",
10
+ headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" }
11
+ });
12
+ const payload = await res.json();
13
+ if (!res.ok || !payload?.success || !payload?.data) {
14
+ throw new Error(payload?.error || "Failed to load support data.");
15
+ }
16
+ return payload.data;
17
+ }
18
+ async function fetchThread(token) {
19
+ if (!apiUrl || !apiKey) throw new Error("Missing VICKET_API_URL or VICKET_API_KEY.");
20
+ const res = await fetch(
21
+ `${apiUrl}/public/support/ticket?token=${encodeURIComponent(token)}`,
22
+ {
23
+ method: "GET",
24
+ cache: "no-store",
25
+ headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" }
26
+ }
27
+ );
28
+ const payload = await res.json();
29
+ if (!res.ok || !payload?.success || !payload?.data) {
30
+ if (payload?.error_code === "ticket-link-expired") {
31
+ throw new Error("This link has expired. A new secure link has been sent to your email.");
32
+ }
33
+ throw new Error(payload?.error || "Failed to load ticket.");
34
+ }
35
+ return payload.data;
36
+ }
37
+ return { fetchInit, fetchThread };
38
+ }
39
+ export {
40
+ createServerClient
41
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vicket",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Vicket support widget SDK",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,12 +17,15 @@
17
17
  "types": "./dist/next.d.ts"
18
18
  }
19
19
  },
20
- "files": ["dist"],
20
+ "files": [
21
+ "dist"
22
+ ],
21
23
  "scripts": {
22
24
  "build": "tsup",
23
25
  "dev": "tsup --watch"
24
26
  },
25
27
  "devDependencies": {
28
+ "@types/node": "^22.0.0",
26
29
  "tsup": "^8.0.0",
27
30
  "typescript": "^5.9.0"
28
31
  },