@adens/openwa 0.1.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +319 -0
  4. package/bin/openwa.js +11 -0
  5. package/favicon.ico +0 -0
  6. package/logo-long.png +0 -0
  7. package/logo-square.png +0 -0
  8. package/package.json +69 -0
  9. package/prisma/schema.prisma +182 -0
  10. package/server/config.js +29 -0
  11. package/server/database/client.js +11 -0
  12. package/server/database/init.js +28 -0
  13. package/server/express/create-app.js +349 -0
  14. package/server/express/openapi.js +853 -0
  15. package/server/index.js +163 -0
  16. package/server/services/api-key-service.js +131 -0
  17. package/server/services/auth-service.js +162 -0
  18. package/server/services/chat-service.js +1014 -0
  19. package/server/services/session-service.js +81 -0
  20. package/server/socket/register.js +127 -0
  21. package/server/utils/avatar.js +34 -0
  22. package/server/utils/paths.js +29 -0
  23. package/server/whatsapp/adapters/mock-adapter.js +47 -0
  24. package/server/whatsapp/adapters/wwebjs-adapter.js +263 -0
  25. package/server/whatsapp/session-manager.js +356 -0
  26. package/web/components/AppHead.js +14 -0
  27. package/web/components/AuthCard.js +170 -0
  28. package/web/components/BrandLogo.js +11 -0
  29. package/web/components/ChatWindow.js +875 -0
  30. package/web/components/ChatWindow.js.tmp +0 -0
  31. package/web/components/ContactList.js +97 -0
  32. package/web/components/ContactsPanel.js +90 -0
  33. package/web/components/EmojiPicker.js +108 -0
  34. package/web/components/MediaPreviewModal.js +146 -0
  35. package/web/components/MessageActionMenu.js +155 -0
  36. package/web/components/SessionSidebar.js +167 -0
  37. package/web/components/SettingsModal.js +266 -0
  38. package/web/components/Skeletons.js +73 -0
  39. package/web/jsconfig.json +10 -0
  40. package/web/lib/api.js +33 -0
  41. package/web/lib/socket.js +9 -0
  42. package/web/pages/_app.js +5 -0
  43. package/web/pages/dashboard.js +541 -0
  44. package/web/pages/index.js +62 -0
  45. package/web/postcss.config.js +10 -0
  46. package/web/public/favicon.ico +0 -0
  47. package/web/public/logo-long.png +0 -0
  48. package/web/public/logo-square.png +0 -0
  49. package/web/store/useAppStore.js +209 -0
  50. package/web/styles/globals.css +52 -0
  51. package/web/tailwind.config.js +36 -0
@@ -0,0 +1,209 @@
1
+ import { create } from "zustand";
2
+
3
+ function readToken() {
4
+ if (typeof window === "undefined") {
5
+ return null;
6
+ }
7
+
8
+ return window.localStorage.getItem("openwa-token");
9
+ }
10
+
11
+ function writeToken(token) {
12
+ if (typeof window === "undefined") {
13
+ return;
14
+ }
15
+
16
+ if (token) {
17
+ window.localStorage.setItem("openwa-token", token);
18
+ return;
19
+ }
20
+
21
+ window.localStorage.removeItem("openwa-token");
22
+ }
23
+
24
+ function recentChatTimestamp(chat) {
25
+ return chat?.contact?.lastMessageAt || chat?.lastMessage?.createdAt || null;
26
+ }
27
+
28
+ function sortChats(chats) {
29
+ return [...chats].sort((left, right) => {
30
+ const leftRecent = recentChatTimestamp(left);
31
+ const rightRecent = recentChatTimestamp(right);
32
+
33
+ if (leftRecent && rightRecent) {
34
+ return new Date(rightRecent) - new Date(leftRecent);
35
+ }
36
+
37
+ if (rightRecent) {
38
+ return 1;
39
+ }
40
+
41
+ if (leftRecent) {
42
+ return -1;
43
+ }
44
+
45
+ return new Date(right.updatedAt) - new Date(left.updatedAt);
46
+ });
47
+ }
48
+
49
+ export const useAppStore = create((set, get) => ({
50
+ token: null,
51
+ user: null,
52
+ sessions: [],
53
+ chats: [],
54
+ activeChatId: null,
55
+ activeSessionId: null,
56
+ messagesByChat: {},
57
+ messageMetaByChat: {},
58
+ typingByChat: {},
59
+ socket: null,
60
+ hydrateAuth: () => {
61
+ set({ token: readToken() });
62
+ },
63
+ setAuth: ({ token, user }) => {
64
+ writeToken(token);
65
+ set({ token, user });
66
+ },
67
+ logout: () => {
68
+ writeToken(null);
69
+ get().socket?.close();
70
+ set({
71
+ token: null,
72
+ user: null,
73
+ sessions: [],
74
+ chats: [],
75
+ activeChatId: null,
76
+ activeSessionId: null,
77
+ messagesByChat: {},
78
+ messageMetaByChat: {},
79
+ typingByChat: {},
80
+ socket: null
81
+ });
82
+ },
83
+ setBootstrapData: (payload) => {
84
+ const sortedChats = sortChats(payload.chats || []);
85
+ set((state) => ({
86
+ user: payload.user,
87
+ sessions: payload.sessions || [],
88
+ chats: sortedChats,
89
+ activeChatId: payload.activeChatId || sortedChats[0]?.id || null,
90
+ activeSessionId: state.activeSessionId || null,
91
+ messagesByChat: payload.activeChatId
92
+ ? {
93
+ ...state.messagesByChat,
94
+ [payload.activeChatId]: payload.messages || []
95
+ }
96
+ : state.messagesByChat,
97
+ messageMetaByChat: payload.activeChatId
98
+ ? {
99
+ ...state.messageMetaByChat,
100
+ [payload.activeChatId]: {
101
+ hasMore: Boolean(payload.hasMoreMessages),
102
+ nextBefore: payload.nextBefore || null
103
+ }
104
+ }
105
+ : state.messageMetaByChat
106
+ }));
107
+ },
108
+ setMessages: (chatId, messages, meta = null) => {
109
+ set((state) => ({
110
+ messagesByChat: {
111
+ ...state.messagesByChat,
112
+ [chatId]: messages
113
+ },
114
+ messageMetaByChat: meta
115
+ ? {
116
+ ...state.messageMetaByChat,
117
+ [chatId]: meta
118
+ }
119
+ : state.messageMetaByChat
120
+ }));
121
+ },
122
+ prependMessages: (chatId, messages, meta) => {
123
+ set((state) => ({
124
+ messagesByChat: {
125
+ ...state.messagesByChat,
126
+ [chatId]: [...messages, ...(state.messagesByChat[chatId] || [])]
127
+ },
128
+ messageMetaByChat: {
129
+ ...state.messageMetaByChat,
130
+ [chatId]: meta
131
+ }
132
+ }));
133
+ },
134
+ setActiveChat: (chatId) => set({ activeChatId: chatId }),
135
+ setActiveSession: (sessionId) => set({ activeSessionId: sessionId }),
136
+ upsertSession: (session) => {
137
+ set((state) => {
138
+ const normalizedSession = {
139
+ ...session,
140
+ id: session.id || session.sessionId
141
+ };
142
+ const sessions = state.sessions.some((item) => item.id === normalizedSession.id)
143
+ ? state.sessions.map((item) => (item.id === normalizedSession.id ? { ...item, ...normalizedSession } : item))
144
+ : [normalizedSession, ...state.sessions];
145
+
146
+ return {
147
+ sessions,
148
+ activeSessionId: state.activeSessionId || normalizedSession.id || null
149
+ };
150
+ });
151
+ },
152
+ upsertChat: (chat) => {
153
+ set((state) => {
154
+ const chats = state.chats.some((item) => item.id === chat.id)
155
+ ? state.chats.map((item) => (item.id === chat.id ? { ...item, ...chat } : item))
156
+ : [chat, ...state.chats];
157
+
158
+ return {
159
+ chats: sortChats(chats)
160
+ };
161
+ });
162
+ },
163
+ addMessage: (message) => {
164
+ set((state) => ({
165
+ messagesByChat: {
166
+ ...state.messagesByChat,
167
+ [message.chatId]: [...(state.messagesByChat[message.chatId] || []), message]
168
+ }
169
+ }));
170
+ },
171
+ updateMessageStatus: ({ messageId, status }) => {
172
+ set((state) => {
173
+ const nextMessagesByChat = Object.fromEntries(
174
+ Object.entries(state.messagesByChat).map(([chatId, messages]) => [
175
+ chatId,
176
+ messages.map((message) =>
177
+ message.id === messageId
178
+ ? {
179
+ ...message,
180
+ statuses: [...(message.statuses || []), { status, createdAt: new Date().toISOString() }]
181
+ }
182
+ : message
183
+ )
184
+ ])
185
+ );
186
+
187
+ return { messagesByChat: nextMessagesByChat };
188
+ });
189
+ },
190
+ updateMessage: (message) => {
191
+ set((state) => ({
192
+ messagesByChat: Object.fromEntries(
193
+ Object.entries(state.messagesByChat).map(([chatId, messages]) => [
194
+ chatId,
195
+ messages.map((item) => (item.id === message.id ? { ...item, ...message } : item))
196
+ ])
197
+ )
198
+ }));
199
+ },
200
+ setTyping: ({ chatId, isTyping, name, userId }) => {
201
+ set((state) => ({
202
+ typingByChat: {
203
+ ...state.typingByChat,
204
+ [chatId]: { isTyping, name, userId }
205
+ }
206
+ }));
207
+ },
208
+ setSocket: (socket) => set({ socket })
209
+ }));
@@ -0,0 +1,52 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ html,
6
+ body,
7
+ #__next {
8
+ min-height: 100%;
9
+ }
10
+
11
+ body {
12
+ margin: 0;
13
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
14
+ background: #111b21;
15
+ color: #ffffff;
16
+ -webkit-font-smoothing: antialiased;
17
+ -moz-osx-font-smoothing: grayscale;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ button,
25
+ input,
26
+ textarea {
27
+ font: inherit;
28
+ }
29
+
30
+ textarea {
31
+ resize: vertical;
32
+ }
33
+
34
+ input::placeholder,
35
+ textarea::placeholder {
36
+ color: inherit;
37
+ opacity: 0.7;
38
+ }
39
+
40
+ ::-webkit-scrollbar {
41
+ width: 8px;
42
+ height: 8px;
43
+ }
44
+
45
+ ::-webkit-scrollbar-thumb {
46
+ background: rgba(255, 255, 255, 0.12);
47
+ border-radius: 999px;
48
+ }
49
+
50
+ ::-webkit-scrollbar-track {
51
+ background: transparent;
52
+ }
@@ -0,0 +1,36 @@
1
+ const path = require("path");
2
+
3
+ module.exports = {
4
+ content: [
5
+ path.join(__dirname, "pages/**/*.{js,jsx}"),
6
+ path.join(__dirname, "components/**/*.{js,jsx}"),
7
+ path.join(__dirname, "store/**/*.{js,jsx}"),
8
+ path.join(__dirname, "lib/**/*.{js,jsx}")
9
+ ],
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ brand: {
14
+ 50: "#edfdf7",
15
+ 100: "#d2f8e9",
16
+ 500: "#25d366",
17
+ 600: "#1ea952",
18
+ 700: "#167b3c"
19
+ },
20
+ ink: {
21
+ 900: "#111b21",
22
+ 800: "#202c33",
23
+ 700: "#2a3942",
24
+ 500: "#667781",
25
+ 200: "#d1d7db",
26
+ 100: "#e9edef",
27
+ 50: "#f0f2f5"
28
+ }
29
+ },
30
+ boxShadow: {
31
+ panel: "0 1px 3px rgba(17, 27, 33, 0.08), 0 8px 24px rgba(17, 27, 33, 0.06)"
32
+ }
33
+ }
34
+ },
35
+ plugins: []
36
+ };