@medipha/chat-core 1.0.8
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/dist/api/client.d.ts +146 -0
- package/dist/api/queryString.d.ts +10 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +269 -0
- package/dist/reactions.d.ts +41 -0
- package/dist/socket/chatSocket.d.ts +94 -0
- package/package.json +40 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ReactionType, ReactionUsersPage } from '../reactions';
|
|
2
|
+
/**
|
|
3
|
+
* REST client for the chat backend.
|
|
4
|
+
*
|
|
5
|
+
* The module is embedded inside an ERP host, so the base URL and access token are
|
|
6
|
+
* injected at runtime through `configureChatApi` instead of environment variables
|
|
7
|
+
* or cookies.
|
|
8
|
+
*/
|
|
9
|
+
interface ChatApiConfig {
|
|
10
|
+
apiBaseUrl: string;
|
|
11
|
+
getAccessToken: () => string | null;
|
|
12
|
+
}
|
|
13
|
+
/** Payload emitted after a message has transitioned permanently to a tombstone. */
|
|
14
|
+
export interface MessageRemovedPayload {
|
|
15
|
+
conversationId: string;
|
|
16
|
+
messageId: string;
|
|
17
|
+
removedAt: string;
|
|
18
|
+
removedById: string;
|
|
19
|
+
}
|
|
20
|
+
/** Preserves backend error codes so callers can handle expected state races quietly. */
|
|
21
|
+
export declare class ChatApiError extends Error {
|
|
22
|
+
readonly status: number;
|
|
23
|
+
readonly code?: string | undefined;
|
|
24
|
+
constructor(message: string, status: number, code?: string | undefined);
|
|
25
|
+
}
|
|
26
|
+
/** Wires the client to the host-provided API base URL and token accessor. */
|
|
27
|
+
export declare function configureChatApi(nextConfig: ChatApiConfig): void;
|
|
28
|
+
export declare const getMediaUrl: (mediaId: string) => string;
|
|
29
|
+
export declare const resolveMediaUrl: (attachmentUrl?: string | null, mediaId?: string | null) => string | null;
|
|
30
|
+
export declare function getUserById(userId: string): Promise<unknown>;
|
|
31
|
+
export interface GetUsersQuery {
|
|
32
|
+
cursor?: string | null;
|
|
33
|
+
limit?: number;
|
|
34
|
+
search?: string;
|
|
35
|
+
departmentId?: string | null;
|
|
36
|
+
branchId?: string | null;
|
|
37
|
+
}
|
|
38
|
+
export declare function getUsers(query?: GetUsersQuery): Promise<unknown>;
|
|
39
|
+
export declare function getUserBranches(): Promise<unknown>;
|
|
40
|
+
/** Lists branch-level departments (zero UUID) or the direct children of a branch, as
|
|
41
|
+
* `{ value, label }` options shaped for the department filter. */
|
|
42
|
+
export declare function getDepartmentsByBranch(branchId: string): Promise<unknown>;
|
|
43
|
+
export declare function getConversationsByUser(): Promise<unknown>;
|
|
44
|
+
export declare function getConversationById(conversationId: string): Promise<unknown>;
|
|
45
|
+
/** Creates a DIRECT or GROUP conversation. The server notifies every invited member over
|
|
46
|
+
* the socket as soon as this resolves (see the backend's `conversation:created` event) —
|
|
47
|
+
* but this REST call has no live socket of its own to join, so the caller must still emit
|
|
48
|
+
* `conversation:subscribe` with the returned id afterwards to join its own room. */
|
|
49
|
+
export declare function createConversation(payload: {
|
|
50
|
+
type: 'DIRECT' | 'GROUP';
|
|
51
|
+
title?: string;
|
|
52
|
+
avatarUrl?: string;
|
|
53
|
+
memberIds: string[];
|
|
54
|
+
isVerified?: boolean;
|
|
55
|
+
}): Promise<unknown>;
|
|
56
|
+
export declare function updateConversation(conversationId: string, payload: {
|
|
57
|
+
title?: string;
|
|
58
|
+
avatarUrl?: string | null;
|
|
59
|
+
isVerified?: boolean;
|
|
60
|
+
}): Promise<unknown>;
|
|
61
|
+
/** Pins/unpins a conversation for the current user only — a personal sidebar preference. */
|
|
62
|
+
export declare function setConversationPinned(conversationId: string, pinned: boolean): Promise<unknown>;
|
|
63
|
+
export declare function deleteConversation(conversationId: string): Promise<unknown>;
|
|
64
|
+
export declare function addConversationMember(conversationId: string, userId: string): Promise<unknown>;
|
|
65
|
+
/** Adds several users in one request, which the server records as a single "đã thêm" notice
|
|
66
|
+
* naming the first couple of them and counting the rest — one call per user would post one
|
|
67
|
+
* notice per user instead. */
|
|
68
|
+
export declare function addConversationMembers(conversationId: string, userIds: string[]): Promise<unknown>;
|
|
69
|
+
export interface MessageSystemMemberItem {
|
|
70
|
+
userId: string;
|
|
71
|
+
fullName: string;
|
|
72
|
+
avatarUrl: string | null;
|
|
73
|
+
position: number;
|
|
74
|
+
}
|
|
75
|
+
export interface MessageSystemMembersPage {
|
|
76
|
+
totalCount: number;
|
|
77
|
+
items: MessageSystemMemberItem[];
|
|
78
|
+
nextCursorPosition: number | null;
|
|
79
|
+
}
|
|
80
|
+
/** Removes several members in one request, which the server records as a single "đã xóa"
|
|
81
|
+
* notice — the mirror of addConversationMembers. */
|
|
82
|
+
export declare function removeConversationMembers(conversationId: string, userIds: string[]): Promise<unknown>;
|
|
83
|
+
/** People named by a batch member-add notice. Deliberately not part of the message payload:
|
|
84
|
+
* a 500-member batch would otherwise ride along with every history fetch. */
|
|
85
|
+
export declare function getMessageSystemMembers(messageId: string, query?: {
|
|
86
|
+
cursorPosition?: number | null;
|
|
87
|
+
limit?: number;
|
|
88
|
+
}): Promise<MessageSystemMembersPage>;
|
|
89
|
+
interface MembersQuery {
|
|
90
|
+
limit?: number;
|
|
91
|
+
cursor?: string | null;
|
|
92
|
+
search?: string;
|
|
93
|
+
}
|
|
94
|
+
export declare function getConversationMembers(conversationId: string, query?: MembersQuery): Promise<unknown>;
|
|
95
|
+
export declare function deleteConversationMember(conversationId: string, userId: string): Promise<unknown>;
|
|
96
|
+
export declare function leaveConversation(conversationId: string, nextOwnerId: string | null): Promise<unknown>;
|
|
97
|
+
export declare function getLatestMessages(conversationId: string): Promise<unknown>;
|
|
98
|
+
/** Requests a server-authorized tombstone for a message; the actor comes from the access token. */
|
|
99
|
+
export declare function removeMessage(conversationId: string, messageId: string): Promise<MessageRemovedPayload>;
|
|
100
|
+
interface MessageWindowQuery {
|
|
101
|
+
beforeSequence?: string | null;
|
|
102
|
+
afterSequence?: string | null;
|
|
103
|
+
limit?: number;
|
|
104
|
+
}
|
|
105
|
+
export declare function getMessageWindowPage(conversationId: string, query?: MessageWindowQuery): Promise<unknown>;
|
|
106
|
+
export declare function getMessagesAround(conversationId: string, messageId: string): Promise<unknown>;
|
|
107
|
+
export interface GetMessageReactionsQuery {
|
|
108
|
+
type?: ReactionType;
|
|
109
|
+
limit?: number;
|
|
110
|
+
cursorUpdatedAt?: string | null;
|
|
111
|
+
cursorUserId?: string | null;
|
|
112
|
+
}
|
|
113
|
+
/** Loads a page of reaction users only after the message reaction modal is opened. */
|
|
114
|
+
export declare function getMessageReactions(messageId: string, query?: GetMessageReactionsQuery): Promise<ReactionUsersPage>;
|
|
115
|
+
export declare function getPinnedMessages(conversationId: string): Promise<unknown>;
|
|
116
|
+
interface SearchMessagesQuery {
|
|
117
|
+
q?: string;
|
|
118
|
+
messageLimit?: number;
|
|
119
|
+
documentLimit?: number;
|
|
120
|
+
messageBeforeSequence?: string | null;
|
|
121
|
+
documentBeforeSequence?: string | null;
|
|
122
|
+
/** "Người gửi" filter — a conversation member's userId. */
|
|
123
|
+
senderId?: string | null;
|
|
124
|
+
/** "Ngày gửi" filter bounds, inclusive, as "YYYY-MM-DD". Either may be given alone. */
|
|
125
|
+
fromDate?: string | null;
|
|
126
|
+
toDate?: string | null;
|
|
127
|
+
}
|
|
128
|
+
export declare function searchConversationMessages(conversationId: string, query?: SearchMessagesQuery): Promise<unknown>;
|
|
129
|
+
export type ArchiveType = 'media' | 'file' | 'link';
|
|
130
|
+
interface ArchiveQuery {
|
|
131
|
+
type: ArchiveType;
|
|
132
|
+
month?: string | null;
|
|
133
|
+
from?: string | null;
|
|
134
|
+
to?: string | null;
|
|
135
|
+
senderId?: string | null;
|
|
136
|
+
}
|
|
137
|
+
export declare function getConversationArchive(conversationId: string, query: ArchiveQuery): Promise<unknown>;
|
|
138
|
+
export interface UploadMediaResult {
|
|
139
|
+
mediaId: string;
|
|
140
|
+
attachmentUrl: string | null;
|
|
141
|
+
response: unknown;
|
|
142
|
+
}
|
|
143
|
+
export declare function uploadMedia(file: File, conversationId: string, asDocument?: boolean): Promise<UploadMediaResult>;
|
|
144
|
+
/** Uploads an image and points the group avatar at it; only the group owner is allowed. */
|
|
145
|
+
export declare function uploadGroupAvatar(file: File, conversationId: string): Promise<unknown>;
|
|
146
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type QueryParameterValue = string | number | boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Serializes already-selected query parameters without relying on browser globals.
|
|
4
|
+
*
|
|
5
|
+
* React Native does not guarantee `URLSearchParams`, so this uses the same
|
|
6
|
+
* application/x-www-form-urlencoded encoding convention directly. The caller
|
|
7
|
+
* remains responsible for excluding optional values to preserve endpoint rules.
|
|
8
|
+
*/
|
|
9
|
+
export declare function serializeQueryParameters(parameters: ReadonlyArray<readonly [string, QueryParameterValue]>): string;
|
|
10
|
+
export {};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("socket.io-client");function t(e){return e.map(([e,t])=>`${n(e)}=${n(String(t))}`).join(`&`)}function n(e){return encodeURIComponent(e).replace(/[!'()~]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%20/g,`+`)}var r=class extends Error{constructor(e,t,n){super(e),this.status=t,this.code=n,this.name=`ChatApiError`}},i={apiBaseUrl:``,getAccessToken:()=>null};function a(e){i=e}var o=e=>`${i.apiBaseUrl}${e.startsWith(`/`)?e:`/${e}`}`,s=e=>o(`/data/docs/${e}`),c=(e,t)=>e?/^https?:\/\//i.test(e)?e:o(e):t?s(t):null;async function l(e,t={}){let n=new Headers(t.headers),a=i.getAccessToken(),s=t.auth!==!1,c={...t};delete c.auth,t.body instanceof FormData||n.set(`Content-Type`,`application/json`),s&&a&&n.set(`Authorization`,`Bearer ${a}`);let l=await fetch(o(e),{...c,headers:n}),u=l.headers.get(`content-type`)?.includes(`application/json`)?await l.json():null;if(!l.ok){let e=u;throw new r(e?.message??`Request failed with status ${l.status}`,l.status,e?.code)}return u}function u(e){return l(`/users/${e}`)}function d(e={}){let n=[];e.cursor&&n.push([`cursor`,e.cursor]),e.limit&&n.push([`limit`,String(e.limit)]),e.search&&n.push([`search`,e.search]),e.departmentId&&n.push([`departmentId`,e.departmentId]),e.branchId&&n.push([`branchId`,e.branchId]);let r=t(n);return l(r?`/users?${r}`:`/users`)}function f(){return l(`/users/branches`)}function p(e){return l(`/departments/by-branch/${e}`)}function m(){return l(`/conversations`,{method:`GET`,auth:!0})}function h(e){return l(`/conversations/${e}`)}function ee(e){return l(`/conversations`,{method:`POST`,body:JSON.stringify(e)})}function g(e,t){return l(`/conversations/${e}`,{method:`PATCH`,body:JSON.stringify(t)})}function _(e,t){return l(`/conversations/${e}/pin`,{method:`PATCH`,body:JSON.stringify({pinned:t})})}function v(e){return l(`/conversations/${e}`,{method:`DELETE`})}function y(e,t){return l(`/conversations/${e}/members`,{method:`POST`,body:JSON.stringify({userId:t})})}function b(e,t){return l(`/conversations/${e}/members`,{method:`POST`,body:JSON.stringify({userIds:t})})}function x(e,t){return l(`/conversations/${e}/members`,{method:`DELETE`,body:JSON.stringify({userIds:t})})}function te(e,n={}){let r=[[`limit`,String(n.limit??30)]];return n.cursorPosition!==void 0&&n.cursorPosition!==null&&r.push([`cursorPosition`,String(n.cursorPosition)]),l(`/messages/${e}/system-members?${t(r)}`)}function S(e,n={}){let r=[[`limit`,String(n.limit??30)]];return n.cursor&&r.push([`cursor`,String(n.cursor)]),n.search?.trim()&&r.push([`search`,n.search.trim()]),l(`/conversations/${e}/members?${t(r)}`)}function C(e,t){return l(`/conversations/${e}/members/${t}`,{method:`DELETE`})}function w(e,t){return l(`/conversations/${e}/leave`,{method:`PATCH`,body:JSON.stringify({nextOwnerId:t})})}function T(e){return l(`/messages/conversation/${e}/latest`)}function E(e,t){return l(`/messages/conversation/${e}/${t}`,{method:`DELETE`})}function D(e,n={}){let r=[],i=n.beforeSequence!==void 0&&n.beforeSequence!==null,a=n.afterSequence!==void 0&&n.afterSequence!==null;if(i===a)throw Error(`Message history requires exactly one cursor direction`);return i&&r.push([`beforeSequence`,String(n.beforeSequence)]),a&&r.push([`afterSequence`,String(n.afterSequence)]),r.push([`limit`,String(n.limit??20)]),l(`/messages/conversation/${e}?${t(r)}`)}function O(e,t){return l(`/messages/conversation/${e}/around/${t}`)}function k(e,n={}){let r=[[`limit`,String(n.limit??30)]];return n.type&&r.push([`type`,n.type]),n.cursorUpdatedAt&&n.cursorUserId&&(r.push([`cursorUpdatedAt`,n.cursorUpdatedAt]),r.push([`cursorUserId`,n.cursorUserId])),l(`/messages/${e}/reactions?${t(r)}`)}function A(e){return l(`/messages/conversation/${e}/pinned`)}function j(e,n={}){let r=String(n.q??``).trim();if(!r)throw Error(`Search keyword is required`);let i=[[`q`,r],[`messageLimit`,String(n.messageLimit??20)],[`documentLimit`,String(n.documentLimit??20)]];return n.messageBeforeSequence&&i.push([`messageBeforeSequence`,String(n.messageBeforeSequence)]),n.documentBeforeSequence&&i.push([`documentBeforeSequence`,String(n.documentBeforeSequence)]),n.senderId&&i.push([`senderId`,n.senderId]),n.fromDate&&i.push([`fromDate`,n.fromDate]),n.toDate&&i.push([`toDate`,n.toDate]),l(`/messages/conversation/${e}/search?${t(i)}`)}function M(e,n){let r=[[`type`,n.type]];return n.month&&r.push([`month`,n.month]),n.from&&r.push([`from`,n.from]),n.to&&r.push([`to`,n.to]),n.senderId&&r.push([`senderId`,n.senderId]),l(`/messages/conversation/${e}/archive?${t(r)}`)}async function N(e,t,n){let r=new FormData;r.append(`file`,e),r.append(`conversationId`,t),n&&r.append(`asDocument`,`true`);let i=await l(`/media/uploads`,{method:`POST`,body:r}),a=i?.id;if(!a)throw Error(`Not found media-id response`);return{mediaId:a,attachmentUrl:i?.url??null,response:i}}async function P(e,t){let{mediaId:n}=await N(e,t);return g(t,{avatarUrl:`/data/docs/${n}`})}var F=``,I=null,L=``,R=new Set,z=!1,B=!1;function V(e){F=e}function H(e){let t=I;if(!t)return()=>void 0;let n=[],r=(e,r)=>{r&&(t.on(e,r),n.push({event:e,listener:r}))};if(r(`conversation:new`,e.onConversationNew),r(`conversation:deleted`,e.onConversationDeleted),r(`conversation:updated`,e.onConversationUpdated),r(`conversation:pinned`,e.onConversationPinned),r(`conversation:membership:changed`,e.onConversationMembershipChanged),e.onMessageSent&&(r(`message:sent`,e.onMessageSent),r(`message:ack`,e.onMessageSent)),e.onMessageNew&&(r(`message:new`,e.onMessageNew),r(`message:created`,e.onMessageNew)),r(`message:removed`,e.onMessageRemoved),r(`message:reaction:updated`,e.onMessageReactionUpdated),r(`message:pinned`,e.onMessagePinned),r(`conversation:read`,e.onConversationRead),r(`conversation:unread`,e.onConversationUnread),r(`presence:changed`,e.onPresenceChanged),r(`typing:start`,e.onTypingStart),r(`typing:stop`,e.onTypingStop),(e.onConnected||e.onConnectionRestored)&&r(`connect`,()=>{e.onConnected?.(),B&&e.onConnectionRestored?.()}),r(`disconnect`,e.onConnectionLost),e.onConnectFailed){let t=t=>{e.onConnectFailed?.(t)};r(`connect_error`,t),r(`error`,t),r(`exception`,t)}return()=>{n.forEach(({event:e,listener:n})=>t.off(e,n))}}function U(e){let t={handlers:e,detach:()=>void 0};return R.add(t),t.detach=H(e),()=>{t.detach(),R.delete(t)}}function W(){R.forEach(e=>{e.detach(),e.detach=H(e.handlers)})}function G(t,n){let r=I?.auth?.token;return I&&r===t&&L===F?(n&&U(n),I):(I&&$(),I=(0,e.io)(F,{transports:[`websocket`],upgrade:!1,auth:{token:t}}),L=F,I.on(`connect`,()=>{B=z,z=!0}),W(),n&&U(n),I)}function K(){return I}function q(e,t,n){return I?.connected?(n?I.emit(e,t,n):I.emit(e,t),{ok:!0,status:`EMITTED`}):{ok:!1,status:`DISCONNECTED`}}function J(e,t){return q(`conversation:create`,e,t)}function Y(e){return q(`conversation:subscribe`,{conversationId:e})}function X(e){return q(`conversation:unsubscribe`,{conversationId:e})}function Z(e,t){return q(`message:send`,e,t)}function Q(e){return q(`message:reaction:apply`,e)}function ne(e){return q(`message:reaction:remove`,e)}function re(e,t){return q(`message:pin`,e,t)}function ie(e,t){return q(`message:forward`,e,t)}function ae(e,t){return q(`conversation:read`,e,t)}function oe(e){return q(`typing:start`,e)}function se(e){return q(`typing:stop`,e)}function $(){I&&=(I.removeAllListeners(),I.disconnect(),null),L=``,z=!1,B=!1}exports.ChatApiError=r,exports.addConversationMember=y,exports.addConversationMembers=b,exports.configureChatApi=a,exports.configureSocket=V,exports.connectSocket=G,exports.createConversation=ee,exports.deleteConversation=v,exports.deleteConversationMember=C,exports.disconnectSocket=$,exports.emitConversationRead=ae,exports.emitCreateConversation=J,exports.emitMessageForward=ie,exports.emitMessagePin=re,exports.emitMessageReactionApply=Q,exports.emitMessageReactionRemove=ne,exports.emitMessageSend=Z,exports.emitTypingStart=oe,exports.emitTypingStop=se,exports.getConversationArchive=M,exports.getConversationById=h,exports.getConversationMembers=S,exports.getConversationsByUser=m,exports.getDepartmentsByBranch=p,exports.getLatestMessages=T,exports.getMediaUrl=s,exports.getMessageReactions=k,exports.getMessageSystemMembers=te,exports.getMessageWindowPage=D,exports.getMessagesAround=O,exports.getPinnedMessages=A,exports.getSocket=K,exports.getUserBranches=f,exports.getUserById=u,exports.getUsers=d,exports.leaveConversation=w,exports.removeConversationMembers=x,exports.removeMessage=E,exports.resolveMediaUrl=c,exports.searchConversationMessages=j,exports.setConversationPinned=_,exports.subscribeConversation=Y,exports.subscribeSocketHandlers=U,exports.unsubscribeConversation=X,exports.updateConversation=g,exports.uploadGroupAvatar=P,exports.uploadMedia=N;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { io as e } from "socket.io-client";
|
|
2
|
+
//#region src/api/queryString.ts
|
|
3
|
+
function t(e) {
|
|
4
|
+
return e.map(([e, t]) => `${n(e)}=${n(String(t))}`).join("&");
|
|
5
|
+
}
|
|
6
|
+
function n(e) {
|
|
7
|
+
return encodeURIComponent(e).replace(/[!'()~]/g, (e) => `%${e.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%20/g, "+");
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/api/client.ts
|
|
11
|
+
var r = class extends Error {
|
|
12
|
+
constructor(e, t, n) {
|
|
13
|
+
super(e), this.status = t, this.code = n, this.name = "ChatApiError";
|
|
14
|
+
}
|
|
15
|
+
}, i = {
|
|
16
|
+
apiBaseUrl: "",
|
|
17
|
+
getAccessToken: () => null
|
|
18
|
+
};
|
|
19
|
+
function a(e) {
|
|
20
|
+
i = e;
|
|
21
|
+
}
|
|
22
|
+
var o = (e) => `${i.apiBaseUrl}${e.startsWith("/") ? e : `/${e}`}`, s = (e) => o(`/data/docs/${e}`), c = (e, t) => e ? /^https?:\/\//i.test(e) ? e : o(e) : t ? s(t) : null;
|
|
23
|
+
async function l(e, t = {}) {
|
|
24
|
+
let n = new Headers(t.headers), a = i.getAccessToken(), s = t.auth !== !1, c = { ...t };
|
|
25
|
+
delete c.auth, t.body instanceof FormData || n.set("Content-Type", "application/json"), s && a && n.set("Authorization", `Bearer ${a}`);
|
|
26
|
+
let l = await fetch(o(e), {
|
|
27
|
+
...c,
|
|
28
|
+
headers: n
|
|
29
|
+
}), u = l.headers.get("content-type")?.includes("application/json") ? await l.json() : null;
|
|
30
|
+
if (!l.ok) {
|
|
31
|
+
let e = u;
|
|
32
|
+
throw new r(e?.message ?? `Request failed with status ${l.status}`, l.status, e?.code);
|
|
33
|
+
}
|
|
34
|
+
return u;
|
|
35
|
+
}
|
|
36
|
+
function u(e) {
|
|
37
|
+
return l(`/users/${e}`);
|
|
38
|
+
}
|
|
39
|
+
function d(e = {}) {
|
|
40
|
+
let n = [];
|
|
41
|
+
e.cursor && n.push(["cursor", e.cursor]), e.limit && n.push(["limit", String(e.limit)]), e.search && n.push(["search", e.search]), e.departmentId && n.push(["departmentId", e.departmentId]), e.branchId && n.push(["branchId", e.branchId]);
|
|
42
|
+
let r = t(n);
|
|
43
|
+
return l(r ? `/users?${r}` : "/users");
|
|
44
|
+
}
|
|
45
|
+
function f() {
|
|
46
|
+
return l("/users/branches");
|
|
47
|
+
}
|
|
48
|
+
function p(e) {
|
|
49
|
+
return l(`/departments/by-branch/${e}`);
|
|
50
|
+
}
|
|
51
|
+
function m() {
|
|
52
|
+
return l("/conversations", {
|
|
53
|
+
method: "GET",
|
|
54
|
+
auth: !0
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function h(e) {
|
|
58
|
+
return l(`/conversations/${e}`);
|
|
59
|
+
}
|
|
60
|
+
function ee(e) {
|
|
61
|
+
return l("/conversations", {
|
|
62
|
+
method: "POST",
|
|
63
|
+
body: JSON.stringify(e)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function g(e, t) {
|
|
67
|
+
return l(`/conversations/${e}`, {
|
|
68
|
+
method: "PATCH",
|
|
69
|
+
body: JSON.stringify(t)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function _(e, t) {
|
|
73
|
+
return l(`/conversations/${e}/pin`, {
|
|
74
|
+
method: "PATCH",
|
|
75
|
+
body: JSON.stringify({ pinned: t })
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function v(e) {
|
|
79
|
+
return l(`/conversations/${e}`, { method: "DELETE" });
|
|
80
|
+
}
|
|
81
|
+
function y(e, t) {
|
|
82
|
+
return l(`/conversations/${e}/members`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
body: JSON.stringify({ userId: t })
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function b(e, t) {
|
|
88
|
+
return l(`/conversations/${e}/members`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: JSON.stringify({ userIds: t })
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function x(e, t) {
|
|
94
|
+
return l(`/conversations/${e}/members`, {
|
|
95
|
+
method: "DELETE",
|
|
96
|
+
body: JSON.stringify({ userIds: t })
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function te(e, n = {}) {
|
|
100
|
+
let r = [["limit", String(n.limit ?? 30)]];
|
|
101
|
+
return n.cursorPosition !== void 0 && n.cursorPosition !== null && r.push(["cursorPosition", String(n.cursorPosition)]), l(`/messages/${e}/system-members?${t(r)}`);
|
|
102
|
+
}
|
|
103
|
+
function S(e, n = {}) {
|
|
104
|
+
let r = [["limit", String(n.limit ?? 30)]];
|
|
105
|
+
return n.cursor && r.push(["cursor", String(n.cursor)]), n.search?.trim() && r.push(["search", n.search.trim()]), l(`/conversations/${e}/members?${t(r)}`);
|
|
106
|
+
}
|
|
107
|
+
function C(e, t) {
|
|
108
|
+
return l(`/conversations/${e}/members/${t}`, { method: "DELETE" });
|
|
109
|
+
}
|
|
110
|
+
function w(e, t) {
|
|
111
|
+
return l(`/conversations/${e}/leave`, {
|
|
112
|
+
method: "PATCH",
|
|
113
|
+
body: JSON.stringify({ nextOwnerId: t })
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function T(e) {
|
|
117
|
+
return l(`/messages/conversation/${e}/latest`);
|
|
118
|
+
}
|
|
119
|
+
function E(e, t) {
|
|
120
|
+
return l(`/messages/conversation/${e}/${t}`, { method: "DELETE" });
|
|
121
|
+
}
|
|
122
|
+
function D(e, n = {}) {
|
|
123
|
+
let r = [], i = n.beforeSequence !== void 0 && n.beforeSequence !== null, a = n.afterSequence !== void 0 && n.afterSequence !== null;
|
|
124
|
+
if (i === a) throw Error("Message history requires exactly one cursor direction");
|
|
125
|
+
return i && r.push(["beforeSequence", String(n.beforeSequence)]), a && r.push(["afterSequence", String(n.afterSequence)]), r.push(["limit", String(n.limit ?? 20)]), l(`/messages/conversation/${e}?${t(r)}`);
|
|
126
|
+
}
|
|
127
|
+
function O(e, t) {
|
|
128
|
+
return l(`/messages/conversation/${e}/around/${t}`);
|
|
129
|
+
}
|
|
130
|
+
function k(e, n = {}) {
|
|
131
|
+
let r = [["limit", String(n.limit ?? 30)]];
|
|
132
|
+
return n.type && r.push(["type", n.type]), n.cursorUpdatedAt && n.cursorUserId && (r.push(["cursorUpdatedAt", n.cursorUpdatedAt]), r.push(["cursorUserId", n.cursorUserId])), l(`/messages/${e}/reactions?${t(r)}`);
|
|
133
|
+
}
|
|
134
|
+
function A(e) {
|
|
135
|
+
return l(`/messages/conversation/${e}/pinned`);
|
|
136
|
+
}
|
|
137
|
+
function j(e, n = {}) {
|
|
138
|
+
let r = String(n.q ?? "").trim();
|
|
139
|
+
if (!r) throw Error("Search keyword is required");
|
|
140
|
+
let i = [
|
|
141
|
+
["q", r],
|
|
142
|
+
["messageLimit", String(n.messageLimit ?? 20)],
|
|
143
|
+
["documentLimit", String(n.documentLimit ?? 20)]
|
|
144
|
+
];
|
|
145
|
+
return n.messageBeforeSequence && i.push(["messageBeforeSequence", String(n.messageBeforeSequence)]), n.documentBeforeSequence && i.push(["documentBeforeSequence", String(n.documentBeforeSequence)]), n.senderId && i.push(["senderId", n.senderId]), n.fromDate && i.push(["fromDate", n.fromDate]), n.toDate && i.push(["toDate", n.toDate]), l(`/messages/conversation/${e}/search?${t(i)}`);
|
|
146
|
+
}
|
|
147
|
+
function M(e, n) {
|
|
148
|
+
let r = [["type", n.type]];
|
|
149
|
+
return n.month && r.push(["month", n.month]), n.from && r.push(["from", n.from]), n.to && r.push(["to", n.to]), n.senderId && r.push(["senderId", n.senderId]), l(`/messages/conversation/${e}/archive?${t(r)}`);
|
|
150
|
+
}
|
|
151
|
+
async function N(e, t, n) {
|
|
152
|
+
let r = new FormData();
|
|
153
|
+
r.append("file", e), r.append("conversationId", t), n && r.append("asDocument", "true");
|
|
154
|
+
let i = await l("/media/uploads", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
body: r
|
|
157
|
+
}), a = i?.id;
|
|
158
|
+
if (!a) throw Error("Not found media-id response");
|
|
159
|
+
return {
|
|
160
|
+
mediaId: a,
|
|
161
|
+
attachmentUrl: i?.url ?? null,
|
|
162
|
+
response: i
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function P(e, t) {
|
|
166
|
+
let { mediaId: n } = await N(e, t);
|
|
167
|
+
return g(t, { avatarUrl: `/data/docs/${n}` });
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/socket/chatSocket.ts
|
|
171
|
+
var F = "", I = null, L = "", R = /* @__PURE__ */ new Set(), z = !1, B = !1;
|
|
172
|
+
function V(e) {
|
|
173
|
+
F = e;
|
|
174
|
+
}
|
|
175
|
+
function H(e) {
|
|
176
|
+
let t = I;
|
|
177
|
+
if (!t) return () => void 0;
|
|
178
|
+
let n = [], r = (e, r) => {
|
|
179
|
+
r && (t.on(e, r), n.push({
|
|
180
|
+
event: e,
|
|
181
|
+
listener: r
|
|
182
|
+
}));
|
|
183
|
+
};
|
|
184
|
+
if (r("conversation:new", e.onConversationNew), r("conversation:deleted", e.onConversationDeleted), r("conversation:updated", e.onConversationUpdated), r("conversation:pinned", e.onConversationPinned), r("conversation:membership:changed", e.onConversationMembershipChanged), e.onMessageSent && (r("message:sent", e.onMessageSent), r("message:ack", e.onMessageSent)), e.onMessageNew && (r("message:new", e.onMessageNew), r("message:created", e.onMessageNew)), r("message:removed", e.onMessageRemoved), r("message:reaction:updated", e.onMessageReactionUpdated), r("message:pinned", e.onMessagePinned), r("conversation:read", e.onConversationRead), r("conversation:unread", e.onConversationUnread), r("presence:changed", e.onPresenceChanged), r("typing:start", e.onTypingStart), r("typing:stop", e.onTypingStop), (e.onConnected || e.onConnectionRestored) && r("connect", () => {
|
|
185
|
+
e.onConnected?.(), B && e.onConnectionRestored?.();
|
|
186
|
+
}), r("disconnect", e.onConnectionLost), e.onConnectFailed) {
|
|
187
|
+
let t = (t) => {
|
|
188
|
+
e.onConnectFailed?.(t);
|
|
189
|
+
};
|
|
190
|
+
r("connect_error", t), r("error", t), r("exception", t);
|
|
191
|
+
}
|
|
192
|
+
return () => {
|
|
193
|
+
n.forEach(({ event: e, listener: n }) => t.off(e, n));
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function U(e) {
|
|
197
|
+
let t = {
|
|
198
|
+
handlers: e,
|
|
199
|
+
detach: () => void 0
|
|
200
|
+
};
|
|
201
|
+
return R.add(t), t.detach = H(e), () => {
|
|
202
|
+
t.detach(), R.delete(t);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function W() {
|
|
206
|
+
R.forEach((e) => {
|
|
207
|
+
e.detach(), e.detach = H(e.handlers);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function G(t, n) {
|
|
211
|
+
let r = I?.auth?.token;
|
|
212
|
+
return I && r === t && L === F ? (n && U(n), I) : (I && $(), I = e(F, {
|
|
213
|
+
transports: ["websocket"],
|
|
214
|
+
upgrade: !1,
|
|
215
|
+
auth: { token: t }
|
|
216
|
+
}), L = F, I.on("connect", () => {
|
|
217
|
+
B = z, z = !0;
|
|
218
|
+
}), W(), n && U(n), I);
|
|
219
|
+
}
|
|
220
|
+
function K() {
|
|
221
|
+
return I;
|
|
222
|
+
}
|
|
223
|
+
function q(e, t, n) {
|
|
224
|
+
return I?.connected ? (n ? I.emit(e, t, n) : I.emit(e, t), {
|
|
225
|
+
ok: !0,
|
|
226
|
+
status: "EMITTED"
|
|
227
|
+
}) : {
|
|
228
|
+
ok: !1,
|
|
229
|
+
status: "DISCONNECTED"
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function J(e, t) {
|
|
233
|
+
return q("conversation:create", e, t);
|
|
234
|
+
}
|
|
235
|
+
function Y(e) {
|
|
236
|
+
return q("conversation:subscribe", { conversationId: e });
|
|
237
|
+
}
|
|
238
|
+
function X(e) {
|
|
239
|
+
return q("conversation:unsubscribe", { conversationId: e });
|
|
240
|
+
}
|
|
241
|
+
function Z(e, t) {
|
|
242
|
+
return q("message:send", e, t);
|
|
243
|
+
}
|
|
244
|
+
function Q(e) {
|
|
245
|
+
return q("message:reaction:apply", e);
|
|
246
|
+
}
|
|
247
|
+
function ne(e) {
|
|
248
|
+
return q("message:reaction:remove", e);
|
|
249
|
+
}
|
|
250
|
+
function re(e, t) {
|
|
251
|
+
return q("message:pin", e, t);
|
|
252
|
+
}
|
|
253
|
+
function ie(e, t) {
|
|
254
|
+
return q("message:forward", e, t);
|
|
255
|
+
}
|
|
256
|
+
function ae(e, t) {
|
|
257
|
+
return q("conversation:read", e, t);
|
|
258
|
+
}
|
|
259
|
+
function oe(e) {
|
|
260
|
+
return q("typing:start", e);
|
|
261
|
+
}
|
|
262
|
+
function se(e) {
|
|
263
|
+
return q("typing:stop", e);
|
|
264
|
+
}
|
|
265
|
+
function $() {
|
|
266
|
+
I &&= (I.removeAllListeners(), I.disconnect(), null), L = "", z = !1, B = !1;
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
export { r as ChatApiError, y as addConversationMember, b as addConversationMembers, a as configureChatApi, V as configureSocket, G as connectSocket, ee as createConversation, v as deleteConversation, C as deleteConversationMember, $ as disconnectSocket, ae as emitConversationRead, J as emitCreateConversation, ie as emitMessageForward, re as emitMessagePin, Q as emitMessageReactionApply, ne as emitMessageReactionRemove, Z as emitMessageSend, oe as emitTypingStart, se as emitTypingStop, M as getConversationArchive, h as getConversationById, S as getConversationMembers, m as getConversationsByUser, p as getDepartmentsByBranch, T as getLatestMessages, s as getMediaUrl, k as getMessageReactions, te as getMessageSystemMembers, D as getMessageWindowPage, O as getMessagesAround, A as getPinnedMessages, K as getSocket, f as getUserBranches, u as getUserById, d as getUsers, w as leaveConversation, x as removeConversationMembers, E as removeMessage, c as resolveMediaUrl, j as searchConversationMessages, _ as setConversationPinned, Y as subscribeConversation, U as subscribeSocketHandlers, X as unsubscribeConversation, g as updateConversation, P as uploadGroupAvatar, N as uploadMedia };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Fixed reaction types supported by the chat protocol. */
|
|
2
|
+
export type ReactionType = 'LIKE' | 'LOVE' | 'HAHA' | 'WOW' | 'SAD' | 'ANGRY';
|
|
3
|
+
/** Aggregate counts delivered with message history and reaction update events. */
|
|
4
|
+
export interface ReactionSummaryPayload {
|
|
5
|
+
totalCount: number;
|
|
6
|
+
totalUsers: number;
|
|
7
|
+
byType: Array<{
|
|
8
|
+
type: ReactionType;
|
|
9
|
+
count: number;
|
|
10
|
+
userCount: number;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
/** Server event emitted after a user applies or explicitly removes a reaction. */
|
|
14
|
+
export interface MessageReactionUpdatedEvent {
|
|
15
|
+
conversationId: string;
|
|
16
|
+
messageId: string;
|
|
17
|
+
userId: string;
|
|
18
|
+
reaction: {
|
|
19
|
+
type: ReactionType;
|
|
20
|
+
count: number;
|
|
21
|
+
} | null;
|
|
22
|
+
summary: ReactionSummaryPayload;
|
|
23
|
+
}
|
|
24
|
+
/** A single user's counted reaction, returned only by the reactions detail endpoint. */
|
|
25
|
+
export interface ReactionUserItem {
|
|
26
|
+
userId: string;
|
|
27
|
+
fullName: string;
|
|
28
|
+
avatarUrl: string | null;
|
|
29
|
+
type: ReactionType;
|
|
30
|
+
count: number;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}
|
|
33
|
+
/** Cursor-paginated reaction detail response. */
|
|
34
|
+
export interface ReactionUsersPage {
|
|
35
|
+
totalCount: number;
|
|
36
|
+
totalUsers: number;
|
|
37
|
+
items: ReactionUserItem[];
|
|
38
|
+
nextCursorUpdatedAt: string | null;
|
|
39
|
+
nextCursorUserId: string | null;
|
|
40
|
+
hasMore: boolean;
|
|
41
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Socket } from 'socket.io-client';
|
|
2
|
+
import { ReactionType } from '../reactions';
|
|
3
|
+
/** Realtime event callbacks the chat screens may register on the shared socket. */
|
|
4
|
+
export interface SocketHandlers {
|
|
5
|
+
onConversationNew?: (payload: unknown) => void;
|
|
6
|
+
onConversationDeleted?: (payload: unknown) => void;
|
|
7
|
+
onConversationUpdated?: (payload: unknown) => void;
|
|
8
|
+
onConversationPinned?: (payload: unknown) => void;
|
|
9
|
+
onConversationMembershipChanged?: (payload: unknown) => void;
|
|
10
|
+
onMessageSent?: (payload: unknown) => void;
|
|
11
|
+
onMessageNew?: (payload: unknown) => void;
|
|
12
|
+
onMessageRemoved?: (payload: unknown) => void;
|
|
13
|
+
onMessageReactionUpdated?: (payload: unknown) => void;
|
|
14
|
+
onMessagePinned?: (payload: unknown) => void;
|
|
15
|
+
onConversationRead?: (payload: unknown) => void;
|
|
16
|
+
onConversationUnread?: (payload: unknown) => void;
|
|
17
|
+
onPresenceChanged?: (payload: unknown) => void;
|
|
18
|
+
onTypingStart?: (payload: unknown) => void;
|
|
19
|
+
onTypingStop?: (payload: unknown) => void;
|
|
20
|
+
/** Fires on every disconnect (network loss, server restart, etc.). */
|
|
21
|
+
onConnectionLost?: () => void;
|
|
22
|
+
/** Fires when the socket reconnects after a prior disconnect — not on the initial connect. */
|
|
23
|
+
onConnectionRestored?: () => void;
|
|
24
|
+
onConnected?: () => void;
|
|
25
|
+
/** Fires when Socket.IO cannot connect or the connected socket emits an error. */
|
|
26
|
+
onConnectFailed?: (error: unknown) => void;
|
|
27
|
+
}
|
|
28
|
+
/** Acknowledgement callback invoked by the server after an emitted event. */
|
|
29
|
+
type AcknowledgementCallback = (acknowledgement: unknown) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Immediate local outcome of an emit attempt. `EMITTED` only confirms the event was handed
|
|
32
|
+
* to Socket.IO; a server acknowledgement remains the source of truth for business errors.
|
|
33
|
+
* Additional transport outcomes can be added to this union without changing callers' shape.
|
|
34
|
+
*/
|
|
35
|
+
export type SocketEmitResult = {
|
|
36
|
+
ok: true;
|
|
37
|
+
status: 'EMITTED';
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
status: 'DISCONNECTED';
|
|
41
|
+
};
|
|
42
|
+
/** Stores the host-provided socket endpoint used by later connect calls. */
|
|
43
|
+
export declare function configureSocket(url: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Registers screen-specific socket listeners without creating a connection. Active
|
|
46
|
+
* subscriptions are rebound when the shared socket is replaced after a token or URL change.
|
|
47
|
+
*/
|
|
48
|
+
export declare function subscribeSocketHandlers(handlers: SocketHandlers): () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Connects (or reuses) the shared socket using the configured URL and given token.
|
|
51
|
+
* New UI code should register event callbacks with subscribeSocketHandlers; the optional
|
|
52
|
+
* handlers argument remains for compatibility with existing core consumers.
|
|
53
|
+
*/
|
|
54
|
+
export declare function connectSocket(accessToken: string, handlers?: SocketHandlers): Socket;
|
|
55
|
+
/** Returns the shared socket instance when one exists. */
|
|
56
|
+
export declare function getSocket(): Socket | null;
|
|
57
|
+
export declare function emitCreateConversation(payload: unknown, onAcknowledgement?: AcknowledgementCallback): SocketEmitResult;
|
|
58
|
+
export declare function subscribeConversation(conversationId: string): SocketEmitResult;
|
|
59
|
+
export declare function unsubscribeConversation(conversationId: string): SocketEmitResult;
|
|
60
|
+
export declare function emitMessageSend(payload: unknown, onAcknowledgement?: AcknowledgementCallback): SocketEmitResult;
|
|
61
|
+
/** Applies a type even when it matches the caller's current type; only remove clears it. */
|
|
62
|
+
export declare function emitMessageReactionApply(payload: {
|
|
63
|
+
conversationId: string;
|
|
64
|
+
messageId: string;
|
|
65
|
+
type: ReactionType;
|
|
66
|
+
}): SocketEmitResult;
|
|
67
|
+
/** Removes the caller's reaction through the protocol's explicit remove action. */
|
|
68
|
+
export declare function emitMessageReactionRemove(payload: {
|
|
69
|
+
conversationId: string;
|
|
70
|
+
messageId: string;
|
|
71
|
+
}): SocketEmitResult;
|
|
72
|
+
export declare function emitMessagePin(payload: {
|
|
73
|
+
conversationId: string;
|
|
74
|
+
messageId: string;
|
|
75
|
+
pinned: boolean;
|
|
76
|
+
}, onAcknowledgement?: AcknowledgementCallback): SocketEmitResult;
|
|
77
|
+
export declare function emitMessageForward(payload: {
|
|
78
|
+
sourceMessageId: string;
|
|
79
|
+
targetConversationIds: string[];
|
|
80
|
+
clientMessageIds: Record<string, string>;
|
|
81
|
+
}, onAcknowledgement?: AcknowledgementCallback): SocketEmitResult;
|
|
82
|
+
export declare function emitConversationRead(payload: {
|
|
83
|
+
conversationId: string;
|
|
84
|
+
sequence: string;
|
|
85
|
+
}, onAcknowledgement?: AcknowledgementCallback): SocketEmitResult;
|
|
86
|
+
/** Typing is ephemeral — best-effort, so a missing/disconnected socket is a silent no-op. */
|
|
87
|
+
export declare function emitTypingStart(payload: {
|
|
88
|
+
conversationId: string;
|
|
89
|
+
}): SocketEmitResult;
|
|
90
|
+
export declare function emitTypingStop(payload: {
|
|
91
|
+
conversationId: string;
|
|
92
|
+
}): SocketEmitResult;
|
|
93
|
+
export declare function disconnectSocket(): void;
|
|
94
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@medipha/chat-core",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "Shared REST and Socket.IO client for Meditech chat applications.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org/",
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"private": false,
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"socket.io-client": "^4.8.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.8.3",
|
|
30
|
+
"vite": "^8.1.0",
|
|
31
|
+
"vite-plugin-dts": "latest"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20.19.0 || >=22.12.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json && vite build",
|
|
38
|
+
"dev": "vite build --watch"
|
|
39
|
+
}
|
|
40
|
+
}
|