@minicode-dev/chat 0.1.12 → 0.1.14
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/CHANGELOG.md +112 -5
- package/README.md +401 -309
- package/assets/sounds/effects/avalanche.mp3 +0 -0
- package/assets/sounds/racing/car_boost.mp3 +0 -0
- package/assets/sounds/racing/car_boost.ogg +0 -0
- package/assets/sounds/racing/car_bump.mp3 +0 -0
- package/assets/sounds/racing/car_bump.ogg +0 -0
- package/assets/sounds/racing/car_drift_loop.mp3 +0 -0
- package/assets/sounds/racing/car_drift_loop.ogg +0 -0
- package/assets/sounds/racing/car_engine_loop.mp3 +0 -0
- package/assets/sounds/racing/car_engine_loop.ogg +0 -0
- package/assets/sounds/racing/car_engine_start.mp3 +0 -0
- package/assets/sounds/racing/car_engine_start.ogg +0 -0
- package/assets/sounds/racing/countdown_beep.mp3 +0 -0
- package/assets/sounds/racing/countdown_beep.ogg +0 -0
- package/assets/sounds/racing/countdown_go.mp3 +0 -0
- package/assets/sounds/racing/countdown_go.ogg +0 -0
- package/assets/sounds/racing/final_lap.mp3 +0 -0
- package/assets/sounds/racing/final_lap.ogg +0 -0
- package/assets/sounds/racing/finish_line.mp3 +0 -0
- package/assets/sounds/racing/finish_line.ogg +0 -0
- package/assets/sounds/racing/lap_complete.mp3 +0 -0
- package/assets/sounds/racing/lap_complete.ogg +0 -0
- package/assets/sounds/racing/music_circuit.mp3 +0 -0
- package/assets/sounds/racing/music_circuit.ogg +0 -0
- package/assets/sounds/racing/music_final_lap.mp3 +0 -0
- package/assets/sounds/racing/music_final_lap.ogg +0 -0
- package/assets/sounds/racing/race_defeat.mp3 +0 -0
- package/assets/sounds/racing/race_defeat.ogg +0 -0
- package/assets/sounds/racing/race_victory.mp3 +0 -0
- package/assets/sounds/racing/race_victory.ogg +0 -0
- package/assets/sounds/tanks/barrel_explode.mp3 +0 -0
- package/assets/sounds/tanks/barrel_explode.ogg +0 -0
- package/assets/sounds/tanks/crate_break.mp3 +0 -0
- package/assets/sounds/tanks/crate_break.ogg +0 -0
- package/assets/sounds/tanks/health_pickup.mp3 +0 -0
- package/assets/sounds/tanks/health_pickup.ogg +0 -0
- package/assets/sounds/tanks/last_life.mp3 +0 -0
- package/assets/sounds/tanks/last_life.ogg +0 -0
- package/assets/sounds/tanks/life_lost.mp3 +0 -0
- package/assets/sounds/tanks/life_lost.ogg +0 -0
- package/assets/sounds/tanks/respawn.mp3 +0 -0
- package/assets/sounds/tanks/respawn.ogg +0 -0
- package/assets/sounds/tanks/shield_end.mp3 +0 -0
- package/assets/sounds/tanks/shield_end.ogg +0 -0
- package/assets/sounds/tanks/shield_loop.mp3 +0 -0
- package/assets/sounds/tanks/shield_loop.ogg +0 -0
- package/assets/sounds/tanks/wall_hit.mp3 +0 -0
- package/assets/sounds/tanks/wall_hit.ogg +0 -0
- package/dist/chunks/{BattleshipGameCard-iRk8d4vR.js → BattleshipGameCard-B-B4r7lK.js} +146 -144
- package/dist/chunks/{BattleshipGameCard-iRk8d4vR.js.map → BattleshipGameCard-B-B4r7lK.js.map} +1 -1
- package/dist/chunks/{ChatContext-DjIEifGY.js → ChatContext-DQtKsR6Z.js} +2 -2
- package/dist/chunks/{ChatContext-DjIEifGY.js.map → ChatContext-DQtKsR6Z.js.map} +1 -1
- package/dist/chunks/RacingLiveGameCard-c1G7uoDI.js +1654 -0
- package/dist/chunks/RacingLiveGameCard-c1G7uoDI.js.map +1 -0
- package/dist/chunks/{TanksGameCard-B47ZQcSr.js → TanksGameCard-CvTaF7tu.js} +164 -162
- package/dist/chunks/{TanksGameCard-B47ZQcSr.js.map → TanksGameCard-CvTaF7tu.js.map} +1 -1
- package/dist/chunks/TanksLiveGameCard-COr9Oay5.js +1011 -0
- package/dist/chunks/TanksLiveGameCard-COr9Oay5.js.map +1 -0
- package/dist/chunks/avalancheScene3d-Z5QyZIid.js +1299 -0
- package/dist/chunks/avalancheScene3d-Z5QyZIid.js.map +1 -0
- package/dist/chunks/connect4Sounds-DmD-E9kX.js +235 -0
- package/dist/chunks/connect4Sounds-DmD-E9kX.js.map +1 -0
- package/dist/chunks/connectionStore-DwgIUl30.js +13 -0
- package/dist/chunks/connectionStore-DwgIUl30.js.map +1 -0
- package/dist/chunks/createLiveScene-CWBunxPi.js +367 -0
- package/dist/chunks/createLiveScene-CWBunxPi.js.map +1 -0
- package/dist/chunks/createRaceScene-C7Rw89kj.js +305 -0
- package/dist/chunks/createRaceScene-C7Rw89kj.js.map +1 -0
- package/dist/chunks/{createTankScene-CV7Brxmt.js → createTankScene-yhX6la27.js} +150 -147
- package/dist/chunks/{createTankScene-CV7Brxmt.js.map → createTankScene-yhX6la27.js.map} +1 -1
- package/dist/chunks/decor-CyyCnmct.js +418 -0
- package/dist/chunks/decor-CyyCnmct.js.map +1 -0
- package/dist/chunks/fx-1ZwzmPWj.js +458 -0
- package/dist/chunks/fx-1ZwzmPWj.js.map +1 -0
- package/dist/chunks/fx-UZwhOmdl.js +584 -0
- package/dist/chunks/fx-UZwhOmdl.js.map +1 -0
- package/dist/chunks/gameStatus-BChd29yI.js +62 -0
- package/dist/chunks/gameStatus-BChd29yI.js.map +1 -0
- package/dist/chunks/{connect4Sounds-Cv0q31VW.js → games-BvYTWgaR.js} +814 -951
- package/dist/chunks/games-BvYTWgaR.js.map +1 -0
- package/dist/chunks/rolldown-runtime-CGnMp29N.js +10 -0
- package/dist/chunks/tank-DjEQ4FXF.js +553 -0
- package/dist/chunks/tank-DjEQ4FXF.js.map +1 -0
- package/dist/chunks/tanks-live-BcKmrhnV.js +261 -0
- package/dist/chunks/tanks-live-BcKmrhnV.js.map +1 -0
- package/dist/chunks/{tanksSounds-CUNDIOal.js → tanksSounds-DwjfSp3W.js} +43 -11
- package/dist/chunks/tanksSounds-DwjfSp3W.js.map +1 -0
- package/dist/chunks/track-BcnnpnAm.js +259 -0
- package/dist/chunks/track-BcnnpnAm.js.map +1 -0
- package/dist/chunks/util-DwI9bfS7.js +57 -0
- package/dist/chunks/util-DwI9bfS7.js.map +1 -0
- package/dist/index.js +2176 -1592
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/index.d.ts +4 -2
- package/package.json +1 -1
- package/dist/chunks/TanksLiveGameCard-DGsnMD3j.js +0 -1225
- package/dist/chunks/TanksLiveGameCard-DGsnMD3j.js.map +0 -1
- package/dist/chunks/connect4Sounds-Cv0q31VW.js.map +0 -1
- package/dist/chunks/createLiveScene-BqI7qV4n.js +0 -364
- package/dist/chunks/createLiveScene-BqI7qV4n.js.map +0 -1
- package/dist/chunks/fx-BHsl4S_J.js +0 -1470
- package/dist/chunks/fx-BHsl4S_J.js.map +0 -1
- package/dist/chunks/gameStatus-Cu1Z8un9.js +0 -69
- package/dist/chunks/gameStatus-Cu1Z8un9.js.map +0 -1
- package/dist/chunks/tanksSounds-CUNDIOal.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,59 +1,86 @@
|
|
|
1
1
|
# @minicode-dev/chat
|
|
2
2
|
|
|
3
|
-
Real-time chat for React
|
|
4
|
-
attachments, notifications,
|
|
3
|
+
Real-time chat for React: WebSocket transport, reactions, editing, replies, emoji, GIFs/stickers via KLIPY,
|
|
4
|
+
file and voice attachments, YouTube players, notifications, animated effects and four two-player games.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
A standalone, reusable npm package. The host application supplies the transport, authentication and served
|
|
7
|
+
assets through configuration; nothing is tied to a specific backend or application.
|
|
8
8
|
|
|
9
|
-
- Owner: **Minicode** · License: `UNLICENSED
|
|
9
|
+
- Owner: **Minicode** · License: `UNLICENSED` · Public on npm (`publishConfig.access: public`).
|
|
10
|
+
- The UI copy is in Spanish.
|
|
10
11
|
|
|
11
|
-
##
|
|
12
|
+
## Features
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
| Feature | What you get | Host requirements |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Messaging | Private chats and groups, replies, editing, reactions, presence, user search, unread badges | WebSocket backend speaking the chat protocol (see [Wire protocol](#wire-protocol)) |
|
|
17
|
+
| Emoji | Unicode picker (English/Spanish search, recents, favorites, skin tones), `:shortcode` autocomplete, emoticons such as `:)` and `<3` | Bundler with JSON `import()` support |
|
|
18
|
+
| Custom emoji | Image emojis per provider in messages, picker, autocomplete and reactions | `media.emoji.custom` catalog and image hosting |
|
|
19
|
+
| GIFs and stickers | KLIPY search, categories, local favorites/recents | `configureKlipy({ apiKey })` and an attachment-capable `sendMessage` |
|
|
20
|
+
| Attachments and voice notes | Files up to 10 MB, in-browser voice recording | Attachment-capable `sendMessage`; microphone permission |
|
|
21
|
+
| YouTube | Inline player for the first supported link per message | CSP `frame-src https://www.youtube-nocookie.com` |
|
|
22
|
+
| Effects | Zumbido, Cerdito disco, Confeti, Avalancha and countdown messages | Served sounds; optional `three` |
|
|
23
|
+
| Games | Conecta 4, Batalla Naval 3D, Tanques de guerra (turn-based), Tanques LIVE 3D (WebRTC) | Games transport bridge; served sounds; optional `three` |
|
|
24
|
+
| Notifications and PWA | Desktop notifications, app badge, message sounds, detached window, install prompt | `<ChatNotifications />` or the `start*` services; your own `/chat` route |
|
|
25
|
+
| Theming | `light`, `dark`, `auto`, `black` | `theme` prop, or the host's `body.dark-mode` / `[data-theme]` |
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
@minicode-dev:registry=https://registry.npmjs.org/
|
|
18
|
-
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
19
|
-
```
|
|
27
|
+
## Installation
|
|
20
28
|
|
|
21
29
|
```bash
|
|
22
30
|
npm install @minicode-dev/chat react react-dom zustand lucide-react
|
|
23
|
-
#
|
|
24
|
-
npm install three
|
|
31
|
+
# Optional, only for the 3D scenes (games and effects fall back without it):
|
|
32
|
+
npm install "three@>=0.134.0 <0.138.0"
|
|
25
33
|
```
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
| Package | Range | Notes |
|
|
35
|
+
| Peer | Range | Notes |
|
|
30
36
|
|---|---|---|
|
|
31
37
|
| `react`, `react-dom` | `>=18` | |
|
|
32
38
|
| `zustand` | `^4.5.0 \|\| ^5.0.0` | |
|
|
33
39
|
| `lucide-react` | `>=0.400.0` | |
|
|
34
|
-
| `three` | `>=0.134.0 <0.138.0` | Optional
|
|
40
|
+
| `three` | `>=0.134.0 <0.138.0` | Optional. Loaded with dynamic `import()` only when a 3D scene starts. |
|
|
41
|
+
|
|
42
|
+
`dayjs` and `emojibase-data` are regular dependencies. The emoji catalog (~1.4 MB of JSON) is loaded with dynamic
|
|
43
|
+
`import()` the first time it is needed, so the host bundler must support JSON imports (Vite, webpack 5,
|
|
44
|
+
esbuild, Rollup with `@rollup/plugin-json`).
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
dynamic `import()` the first time the composer/emoji picker mounts, so the host bundler must support
|
|
38
|
-
JSON imports (Vite, webpack 5, Rollup with `@rollup/plugin-json`, esbuild do).
|
|
46
|
+
The package is ESM only (`dist/index.js`). JavaScript does not import the stylesheet; import it yourself.
|
|
39
47
|
|
|
40
|
-
##
|
|
48
|
+
## Quick start
|
|
41
49
|
|
|
42
50
|
```jsx
|
|
51
|
+
import { useEffect } from 'react';
|
|
43
52
|
import {
|
|
44
53
|
LiraChatProvider, ChatButton, ChatWindow, ChatPage, ChatNotifications,
|
|
54
|
+
chatGamesStore, useChatTransport,
|
|
45
55
|
} from '@minicode-dev/chat';
|
|
46
56
|
import '@minicode-dev/chat/styles.css';
|
|
47
57
|
|
|
48
58
|
const config = {
|
|
49
59
|
websocket: { url: 'wss://chat.example.com/ws' },
|
|
50
60
|
auth: { getToken: () => localStorage.getItem('token') },
|
|
51
|
-
|
|
61
|
+
user: { id: 'user-123', name: 'Ana' },
|
|
62
|
+
callbacks: { onConnectionChange: (status) => console.info('chat', status) },
|
|
52
63
|
};
|
|
53
64
|
|
|
65
|
+
// Games travel as chat text: give the games store a way to send it.
|
|
66
|
+
function ChatGamesBridge() {
|
|
67
|
+
const transport = useChatTransport();
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!transport) return undefined;
|
|
70
|
+
chatGamesStore.getState().configureTransport({
|
|
71
|
+
send: (roomId, text) => transport.request('CHAT_MESSAGE', { room_id: roomId, message: text }),
|
|
72
|
+
getScope: () => transport.getIdentity().scope,
|
|
73
|
+
getUserId: () => transport.getIdentity().userId,
|
|
74
|
+
});
|
|
75
|
+
return () => chatGamesStore.getState().configureTransport(null);
|
|
76
|
+
}, [transport]);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
54
80
|
export function App() {
|
|
55
81
|
return (
|
|
56
82
|
<LiraChatProvider config={config}>
|
|
83
|
+
<ChatGamesBridge />
|
|
57
84
|
<ChatButton label="Chat" />
|
|
58
85
|
<ChatWindow placement="floating" />
|
|
59
86
|
<ChatNotifications />
|
|
@@ -61,136 +88,162 @@ export function App() {
|
|
|
61
88
|
);
|
|
62
89
|
}
|
|
63
90
|
|
|
64
|
-
// Full-page route (
|
|
91
|
+
// Full-page route (default `/chat`), also opened by the "detach" button:
|
|
65
92
|
export function ChatRoute() {
|
|
66
93
|
return (
|
|
67
94
|
<LiraChatProvider config={config}>
|
|
95
|
+
<ChatGamesBridge />
|
|
68
96
|
<ChatPage />
|
|
69
97
|
</LiraChatProvider>
|
|
70
98
|
);
|
|
71
99
|
}
|
|
72
100
|
```
|
|
73
101
|
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- `ChatWindow` props: `placement` (`'floating' | 'modal' | 'embedded'`), `open` / `onOpenChange`
|
|
78
|
-
(controlled mode), `className`, `BattleshipPreviewComponent`.
|
|
79
|
-
- `ChatPage`: `className`. `ChatButton`: `className`, `label`. `ChatNotifications`: no props.
|
|
80
|
-
- Lower level: `createStandaloneTransport`, `createChatStore`, hooks (`useChatConfig`, `useChatTransport`,
|
|
81
|
-
`useChatConnection`, `useChatUnreadTotal`) and presentational primitives (`ChatWindowShell`,
|
|
82
|
-
`MessageList`, `MessageComposer`, pickers…). See `index.d.ts` for the full typed API; anything not
|
|
83
|
-
exported from the package root is internal.
|
|
84
|
-
|
|
85
|
-
## Styles
|
|
86
|
-
|
|
87
|
-
Import the stylesheet once, anywhere in the host app:
|
|
88
|
-
|
|
89
|
-
```js
|
|
90
|
-
import '@minicode-dev/chat/styles.css';
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Assets (sounds)
|
|
102
|
+
Then serve the sounds ([Assets](#assets-sounds)). Without the games bridge, game invitations fail with
|
|
103
|
+
"Chat no disponible". The provider creates its transport and chat store once; remount it with a different
|
|
104
|
+
`key` to change `websocket` or `auth`.
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
`battleship_audio_pack/assets/*.ogg`) but **the host serves them**. By default the chat requests them
|
|
97
|
-
from `/sounds/...`. Either:
|
|
106
|
+
## Host integration checklist
|
|
98
107
|
|
|
99
|
-
1.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
2. Or serve them from another path/CDN and point the chat at it, before any sound plays:
|
|
108
|
+
1. **Styles:** `import '@minicode-dev/chat/styles.css'` once.
|
|
109
|
+
2. **Sounds:** copy `assets/sounds/` to `/sounds/` or call `setChatAssetsBaseUrl()` ([Assets](#assets-sounds)).
|
|
110
|
+
3. **Games:** call `chatGamesStore.getState().configureTransport({ send, getScope, getUserId })` (see Quick start).
|
|
111
|
+
Games only run in private rooms named `private_<userA>_<userB>`.
|
|
112
|
+
4. **Attachments, GIFs, stickers and voice notes** are all sent as attachments. The default store sends text
|
|
113
|
+
only and rejects attachments ("Este transporte no admite adjuntos."). Create the store yourself with an
|
|
114
|
+
attachment-capable `sendMessage(roomId, message, attachments)` and pass it as `chatStore`:
|
|
107
115
|
|
|
108
116
|
```js
|
|
109
|
-
import {
|
|
110
|
-
|
|
117
|
+
import { createChatStore } from '@minicode-dev/chat';
|
|
118
|
+
const chatStore = createChatStore({ transport, sendMessage: mySendMessage });
|
|
119
|
+
// <LiraChatProvider config={config} transport={transport} chatStore={chatStore}>
|
|
111
120
|
```
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
Hosts that own a raw WebSocket can use the exported file relay: `sendRelayMessage(socket, roomId, message,
|
|
123
|
+
attachments)` sends files as 32 KB `CHAT_FILE` frames (one attachment per message, 1 byte to 10 MB);
|
|
124
|
+
`consumeRelayEvent(socket, frame)` and `hydrateRelayMessage(socket, message)` rebuild them on receipt;
|
|
125
|
+
`closeRelay(socket)` rejects pending sends. File bytes only live in connected browsers.
|
|
126
|
+
5. **KLIPY:** `configureKlipy({ apiKey })` before the GIF/sticker pickers open ([GIFs](#gifs-and-stickers)).
|
|
127
|
+
6. **Detached window / PWA:** render `<ChatPage />` at `/chat` (or set `pwa.path`). For the installable app,
|
|
128
|
+
call `startChatApp()` and `registerChatWorker({ path: '/chat', swUrl: '/chat-sw.js' })`; the service worker
|
|
129
|
+
file is yours, the package does not ship one.
|
|
130
|
+
7. **Background services:** `<ChatNotifications />` starts desktop notifications, the app badge and message
|
|
131
|
+
and Connecta 4 sounds while mounted. Hosts can instead start `startDesktopNotifications()`,
|
|
132
|
+
`startAppBadge()` and `startConnect4Sounds()`. Cross-tab sync of unread counts, GIF library and emoji
|
|
133
|
+
preferences: `startUnreadStorageSync()`, `startGifLibrarySync()`, `startEmojiPreferencesSync()`. Every
|
|
134
|
+
starter returns `stop()`.
|
|
116
135
|
|
|
117
|
-
##
|
|
118
|
-
|
|
119
|
-
This repository includes a local React demo that consumes the library source directly:
|
|
136
|
+
## Assets (sounds)
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
npm run dev
|
|
124
|
-
```
|
|
138
|
+
Sounds ship in the package under `assets/sounds/` (also exported as `@minicode-dev/chat/assets/*`), but **the
|
|
139
|
+
host serves them**. By default the chat requests them from `/sounds/...`.
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
| Path in `assets/sounds/` | Used by |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `ting.mp3`/`.ogg`, `notification.mp3`/`.ogg` | New-message sounds |
|
|
144
|
+
| `effects/nudge.mp3`, `cerdito_disco_original.mp3`, `confetti.mp3`, `avalanche.mp3` | Zumbido, Cerdito disco, Confeti, Avalancha |
|
|
145
|
+
| `connect4/*.ogg` (8 files) | Conecta 4 |
|
|
146
|
+
| `battleship_audio_pack/assets/*.ogg` (14 files) | Batalla Naval |
|
|
147
|
+
| `tanks/*.mp3` and `tanks/*.ogg` (12 sounds, both formats) | Tanques de guerra and Tanques LIVE |
|
|
132
148
|
|
|
133
|
-
|
|
149
|
+
Option 1, copy the folder into the host's public directory (Vite/CRA `public/` is served at `/`):
|
|
134
150
|
|
|
135
151
|
```bash
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
npm pack # builds again via prepack and creates an installable .tgz
|
|
152
|
+
mkdir -p public/sounds
|
|
153
|
+
cp -R node_modules/@minicode-dev/chat/assets/sounds/. public/sounds/
|
|
139
154
|
```
|
|
140
155
|
|
|
141
|
-
|
|
156
|
+
Automate it so new sounds arrive with every package update. A cross-platform script (Node 16.7+):
|
|
142
157
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
```js
|
|
159
|
+
// scripts/copy-chat-sounds.mjs
|
|
160
|
+
import { cpSync, mkdirSync } from 'node:fs';
|
|
161
|
+
import { createRequire } from 'node:module';
|
|
162
|
+
import { dirname, join } from 'node:path';
|
|
163
|
+
|
|
164
|
+
const require = createRequire(import.meta.url);
|
|
165
|
+
const packageDir = dirname(require.resolve('@minicode-dev/chat/package.json'));
|
|
166
|
+
const target = join(process.cwd(), 'public', 'sounds');
|
|
167
|
+
|
|
168
|
+
mkdirSync(target, { recursive: true });
|
|
169
|
+
cpSync(join(packageDir, 'assets', 'sounds'), target, { recursive: true });
|
|
170
|
+
console.log(`@minicode-dev/chat sounds copied to ${target}`);
|
|
147
171
|
```
|
|
148
172
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
## Publishing updates
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"scripts": {
|
|
176
|
+
"copy:chat-sounds": "node scripts/copy-chat-sounds.mjs",
|
|
177
|
+
"predev": "npm run copy:chat-sounds",
|
|
178
|
+
"prebuild": "npm run copy:chat-sounds"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
159
182
|
|
|
160
|
-
|
|
183
|
+
Option 2, serve them from another path or CDN and point the chat there before any sound plays:
|
|
161
184
|
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
npm run release -- major # major version
|
|
166
|
-
npm run release -- --dry-run # build and inspect; no version change or publication
|
|
185
|
+
```js
|
|
186
|
+
import { setChatAssetsBaseUrl } from '@minicode-dev/chat';
|
|
187
|
+
setChatAssetsBaseUrl('https://cdn.example.com/minicode-chat/sounds');
|
|
167
188
|
```
|
|
168
189
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
Only `dist/`, `assets/`, public types, README, changelog and package metadata are allowed.
|
|
172
|
-
Temporary archives and this package's root `.tgz` files are removed even on failure.
|
|
173
|
-
Build output in `dist/` is retained for local use. If publication fails after the
|
|
174
|
-
version bump, the updated version remains in `package.json` and the lockfile.
|
|
175
|
-
Registry access follows `publishConfig` in `package.json`; login and any required
|
|
176
|
-
npm authentication challenge are handled by you. No Git push is performed.
|
|
190
|
+
`setChatAssetsBaseUrl(null)` restores `/sounds`. `config.assets.baseUrl` exists in the schema but is not wired;
|
|
191
|
+
use the function. Missing files only silence the sound; visuals and games keep working.
|
|
177
192
|
|
|
178
|
-
|
|
179
|
-
a script with that name that calls `npm publish` could invoke itself recursively.
|
|
193
|
+
## Components and API
|
|
180
194
|
|
|
181
|
-
|
|
195
|
+
| Export | Notes |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `LiraChatProvider` | Props: `config`, `transport` (your own `ChatTransport`; never connected or closed by the provider), `stores`, `chatStore`, `children`. Without `transport`, a standalone WebSocket transport is created from `config.websocket.url`. |
|
|
198
|
+
| `ChatWindow` | `placement`: `'floating'` (portal; minimize, maximize, detach; Escape minimizes), `'modal'` (backdrop and focus trap) or `'embedded'` (inline, always open). Also `open`/`onOpenChange` (controlled), `theme`, `className`, `BattleshipPreviewComponent`. |
|
|
199
|
+
| `ChatPage` | Full-page chat: `theme`, `className`, `BattleshipPreviewComponent`. |
|
|
200
|
+
| `ChatButton` | Launcher with unread badge; works outside the provider. `label`, `className`, `onClick`, `pressed`, `badgeClassName`, `iconSize`. Without `onClick` it toggles `chatUiStore`. |
|
|
201
|
+
| `ChatNotifications` | Renders nothing; runs notification services while mounted (honors `config.notifications`). |
|
|
202
|
+
| Hooks | `useChatConfig`, `useChatTransport`, `useChatConnection`, `useChatIdentity`, `useChatStore`, `useChatStoreApi`, `useChatUnreadTotal`, `useChatUi`, `useChatUnread`, `useChatGames`, `useChatInstall`, `useChatConnectionStatus` |
|
|
203
|
+
| Transport and stores | `createStandaloneTransport`, `assertTransport`, `TRANSPORT_STATUSES`, `createChatStore`, `MAX_CHAT_MESSAGES`, `chatUiStore`, `chatGamesStore`, `chatConnectionStore`, `normalizeConfig`, `DEFAULT_CONFIG` |
|
|
204
|
+
| Protocol helpers | `encodeEdit`, `encodeReaction`, `parseMessageAction`, `isMessageAction`, `groupReactions`, `isStickerAttachment`, `encodeEffect`, `decodeEffect`, `EFFECT_CATALOG`, `getChatEffect` |
|
|
205
|
+
| WebRTC | `createPeerLink`, `createChatSignaling`, `rtcSignals`, `withSignalFaults`, `DEFAULT_ICE_SERVERS`, `WebRtcLab` (lazy, render inside `<Suspense>`), `encodeSignal`, `decodeSignal`, `isWebrtcMessage`, `WEBRTC_PREFIX`, `MAX_SIGNAL_LENGTH` |
|
|
206
|
+
| Presentational | `ChatWindowShell`, `MessageList`, `MessageBubble`, `MessageComposer`, `EmojiPicker`, `GifPicker`, `StickerPicker`, `ChatPeopleContext` |
|
|
182
207
|
|
|
183
|
-
|
|
184
|
-
in the source repository (not included in the npm archive).
|
|
208
|
+
`index.d.ts` has the full typed API. Anything not exported from the package root is internal.
|
|
185
209
|
|
|
186
|
-
|
|
187
|
-
in the source repository. This companion document is not included in the npm archive;
|
|
188
|
-
the integration sections below are included.
|
|
210
|
+
## Configuration
|
|
189
211
|
|
|
190
|
-
|
|
212
|
+
`normalizeConfig` shallow-merges each section over `DEFAULT_CONFIG`. Keys that take effect today:
|
|
191
213
|
|
|
192
|
-
|
|
193
|
-
|
|
214
|
+
| Key | Default | Notes |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| `websocket.url` | `null` | String or (async) function. Used only when no `transport` is injected. |
|
|
217
|
+
| `websocket.reconnect` | `{ max: 3, baseMs: 1000, capMs: 10000 }` | Reconnect backoff. |
|
|
218
|
+
| `websocket.ping` | `false` | `{ action?, everyMs }` keep-alive. |
|
|
219
|
+
| `auth.getToken`, `auth.tokenParam` | `null`, `'token'` | Token added to the WebSocket URL query. |
|
|
220
|
+
| `user`, `scope` | `null` | User `{ id \| user_id \| sub, name \| username \| email }`; scope defaults to the user id. |
|
|
221
|
+
| `features.youtube` | `true` | `false` disables players. |
|
|
222
|
+
| `features.games.connect4`, `.battleship` | `true` | Set each to `false` to hide it. |
|
|
223
|
+
| `features.games.tanks`, `.tanksLive` | `false` | Opt in. |
|
|
224
|
+
| `features.detach` | `{ path: '/chat', windowName: 'lira-independent-chat' }` | `false` hides the detach button. `pwa.path`/`pwa.windowName` override it. |
|
|
225
|
+
| `media.emoji.custom` | `[]` | [Custom emoji](#custom-image-emojis). |
|
|
226
|
+
| `effects` | see [Effects](#effects) | `false` disables effects. |
|
|
227
|
+
| `notifications.desktop`, `.sound`, `.appBadge` | `true` | Read by `<ChatNotifications />`; `notifications: false` disables all three. |
|
|
228
|
+
| `webrtc.iceServers`, `.iceTransportPolicy` | `undefined`, `'all'` | Tanques LIVE STUN/TURN. |
|
|
229
|
+
| `portalContainer` | `document.body` | Element or function for portals. |
|
|
230
|
+
| `callbacks.onOpenChange`, `.onConnectionChange` | no-op | |
|
|
231
|
+
|
|
232
|
+
Accepted and typed but **not wired yet** (no effect): `theme`, `locale`, `storage`, `assets.baseUrl`,
|
|
233
|
+
`media.klipy`, `media.emoji.lazy`, `media.maxAttachmentBytes`, `media.trustedMediaHosts`,
|
|
234
|
+
`notifications.previews` (users toggle previews in the chat header), `websocket.legacyRoomIdRouting`,
|
|
235
|
+
`features.games.battleshipPreview`, the `features` flags `reactions`, `editing`, `replies`, `files`, `voice`,
|
|
236
|
+
`gifs`, `stickers`, `presence`, `groups`, `privateChats` and `install`, and `callbacks.onError`,
|
|
237
|
+
`onOpenConversation` and `onUnreadChange`. `features.games: false` and `features: false` currently do **not**
|
|
238
|
+
hide Conecta 4 or Batalla Naval; disable them with the individual flags.
|
|
239
|
+
|
|
240
|
+
## Emoji
|
|
241
|
+
|
|
242
|
+
- Type `:` plus a name (for example `:smile`) for suggestions; an exact shortcode followed by `:` or a space
|
|
243
|
+
is converted. Classic emoticons (`:)`, `:D`, `;)`, `<3`…) are suggested as their emoji.
|
|
244
|
+
- Recents, favorites and skin tone are stored locally per browser.
|
|
245
|
+
|
|
246
|
+
### Custom image emojis
|
|
194
247
|
|
|
195
248
|
```jsx
|
|
196
249
|
const config = {
|
|
@@ -204,41 +257,50 @@ const config = {
|
|
|
204
257
|
};
|
|
205
258
|
```
|
|
206
259
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
The
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
Open
|
|
239
|
-
and
|
|
240
|
-
|
|
241
|
-
|
|
260
|
+
Shortcodes are lowercase, 1–32 characters of letters, numbers, `_`, `+` or `-`; up to 100 entries; duplicates
|
|
261
|
+
and entries without an HTTP(S), relative or raster `data:` URL are ignored. Unknown shortcodes or failed images
|
|
262
|
+
stay readable as `:team_party:`. The host supplies the shared catalog and owns upload permissions, storage and
|
|
263
|
+
keeping images used in history.
|
|
264
|
+
|
|
265
|
+
## GIFs and stickers
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
import { configureKlipy } from '@minicode-dev/chat';
|
|
269
|
+
configureKlipy({ apiKey: yourKlipyAppKey }); // from your own config; never commit it
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The key is sent from the browser in KLIPY request URLs. Optional `locale` (country code, default `do`) and
|
|
273
|
+
`categoriesLocale` (default `es_DO`). Without a key, the pickers load nothing. Only media from KLIPY's hosts is
|
|
274
|
+
accepted. Favorites and recents are local to the browser. GIFs and stickers are sent as attachments (see the
|
|
275
|
+
checklist).
|
|
276
|
+
|
|
277
|
+
## Attachments and voice notes
|
|
278
|
+
|
|
279
|
+
Files from 1 byte to 10 MB, paste or pick; voice notes use `MediaRecorder` and need microphone permission
|
|
280
|
+
(HTTPS or localhost). Both require an attachment-capable `sendMessage` (see the checklist).
|
|
281
|
+
|
|
282
|
+
## YouTube
|
|
283
|
+
|
|
284
|
+
Supported links: `watch?v=`, `youtu.be`, Shorts, Live and embed URLs (including start times), on
|
|
285
|
+
`youtube.com`, `m.youtube.com`, `music.youtube.com` and `youtube-nocookie.com`. The first video per message is
|
|
286
|
+
embedded from `youtube-nocookie.com`, lazy-loaded and without autoplay; the original text and an
|
|
287
|
+
**Abrir en YouTube** link stay visible. No API key is needed. Videos must allow embedding.
|
|
288
|
+
|
|
289
|
+
## Effects
|
|
290
|
+
|
|
291
|
+
Open **✨ Efectos y zumbidos** in the composer and click an effect to send it. Allowed in private chats
|
|
292
|
+
(and in groups with `allowGroups: true`).
|
|
293
|
+
|
|
294
|
+
| ID | Name | Duration | Visual | Sound (`effects/`) |
|
|
295
|
+
|---|---|---|---|---|
|
|
296
|
+
| `nudge` | Zumbido ⚡ | 7 s | 3D thunderstorm, lightning and shake; Canvas 2D fallback | `nudge.mp3` |
|
|
297
|
+
| `pig-disco` | Cerdito disco 🐷 | 5.2 s | Dancing SVG pig with disco ball | `cerdito_disco_original.mp3` |
|
|
298
|
+
| `confetti` | Confeti 🎉 | 7 s | 3D party (confetti, balloons, gifts, streamers); 220-particle CSS fallback | `confetti.mp3` |
|
|
299
|
+
| `avalanche` | Avalancha 🚀 | 7 s | 3D shower of money, cars, tanks and confetti; CSS fallback | `avalanche.mp3` |
|
|
300
|
+
|
|
301
|
+
3D scenes load `three` lazily and sync with the recording. Without `three`/WebGL or after a lost WebGL context
|
|
302
|
+
they use the fallback. With reduced motion, Confeti and Avalancha use the CSS fallback, Zumbido drops the shake
|
|
303
|
+
and dims the flashes, and CSS animations stop.
|
|
242
304
|
|
|
243
305
|
```jsx
|
|
244
306
|
const config = {
|
|
@@ -256,115 +318,90 @@ const config = {
|
|
|
256
318
|
};
|
|
257
319
|
```
|
|
258
320
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
The
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
New incoming effects automatically animate in the visible conversation by default;
|
|
308
|
-
manual replay is still available. The default cooldown is 5 seconds. Window focus is
|
|
309
|
-
not required; hidden tabs only play the sound (see `backgroundSound`) and old messages do not replay effects. A new permitted
|
|
310
|
-
effect can replace the animation currently playing. Confetti fills the panel with
|
|
311
|
-
220 colored particles and scales to its height. Reduced-motion preferences remain respected.
|
|
312
|
-
Browsers can still block sound before the recipient interacts with the site; the visual
|
|
313
|
-
effect does not depend on successful audio playback. Both participants must use an updated
|
|
314
|
-
package, and explicitly saved `autoPlay: false` settings still disable automatic playback.
|
|
315
|
-
|
|
316
|
-
## Tanques LIVE 3D (preview)
|
|
317
|
-
|
|
318
|
-
This fourth game is independent of turn-based Tanks, Connect 4 and Battleship. Enable it explicitly:
|
|
321
|
+
The values above are the defaults. Behavior:
|
|
322
|
+
|
|
323
|
+
- Received effects appear as cards with **Reproducir efecto** and autoplay in the visible conversation of a
|
|
324
|
+
visible tab. A new effect can replace the one playing. Escape or **×** closes it.
|
|
325
|
+
- With `backgroundSound`, an effect nobody is watching (hidden tab, closed window, another conversation) still
|
|
326
|
+
plays its sound; returning to that conversation within `maxAgeMs` replays it with the animation.
|
|
327
|
+
- History, duplicates and events older than `maxAgeMs` do not autoplay. Sends and autoplay respect
|
|
328
|
+
`cooldownMs`. Same-origin tab deduplication is best effort.
|
|
329
|
+
- The picker's **Sonido** and auto-play switches call `onPreferencesChange({ sound })` / `({ autoPlay })` so the
|
|
330
|
+
host can persist them. Browsers may block audio until the user interacts; visuals do not depend on it.
|
|
331
|
+
- Receiving clients enforce cooldown and age limits, but the server should enforce its own authorization and
|
|
332
|
+
rate limits.
|
|
333
|
+
|
|
334
|
+
### Countdown messages
|
|
335
|
+
|
|
336
|
+
In the effects picker choose **Cuenta regresiva**, a duration (30 s, 1 min, 5 min, 10 min or a custom value in
|
|
337
|
+
seconds or minutes, up to 24 hours), the final effect and whether it plays sound. **Probar efecto** previews it
|
|
338
|
+
locally. The card shows a live clock, hides the chosen effect until zero (also in quotes and previews), animates
|
|
339
|
+
the last ten seconds and plays the effect at zero in the visible conversation. Expired cards offer manual replay.
|
|
340
|
+
Device clocks should be accurate.
|
|
341
|
+
|
|
342
|
+
Effects and countdowns are ordinary text messages (`[minicode-effect:v1]` / `[minicode-countdown:v1]` plus
|
|
343
|
+
JSON and a readable fallback line for older clients). No backend change is needed; the server must preserve the
|
|
344
|
+
text.
|
|
345
|
+
|
|
346
|
+
## Games
|
|
347
|
+
|
|
348
|
+
All games are two-player, in private chats, started from the composer's games menu. Game events are chat
|
|
349
|
+
messages with the `LIRA_GAME_V1:` prefix; they never appear as bubbles or unread messages.
|
|
350
|
+
|
|
351
|
+
| Game | Flag (`features.games`) | Default | Rendering |
|
|
352
|
+
|---|---|---|---|
|
|
353
|
+
| Conecta 4 | `connect4` | `true` | DOM board |
|
|
354
|
+
| Batalla Naval 3D | `battleship` | `true` | `three` scene; 2D grid fallback. Fleets stay private until the final reveal (commit–reveal). |
|
|
355
|
+
| Tanques de guerra (turn-based) | `tanks` | `false` | `three` scene; Canvas 2D fallback. Deterministic simulation on both clients. |
|
|
356
|
+
| Tanques LIVE 3D (real time) | `tanksLive` | `false` | `three` scene; Canvas 2D fallback. WebRTC DataChannels. |
|
|
357
|
+
|
|
358
|
+
Optional Batalla Naval 3D preview (a local prototype opened from the games menu):
|
|
359
|
+
|
|
360
|
+
```jsx
|
|
361
|
+
import { lazy } from 'react';
|
|
362
|
+
import { ChatWindow, loadBattleshipPreview } from '@minicode-dev/chat';
|
|
363
|
+
|
|
364
|
+
const BattleshipPreview = lazy(loadBattleshipPreview);
|
|
365
|
+
// <ChatWindow BattleshipPreviewComponent={BattleshipPreview} />
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Tanques LIVE 3D (preview)
|
|
319
369
|
|
|
320
370
|
```js
|
|
321
371
|
const config = {
|
|
322
372
|
features: { games: { tanksLive: true } },
|
|
323
|
-
// Optional:
|
|
373
|
+
// Optional: STUN/TURN supplied by your application at runtime.
|
|
324
374
|
webrtc: { iceServers, iceTransportPolicy: 'all' },
|
|
325
375
|
};
|
|
326
376
|
```
|
|
327
377
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
4. Wait for the sharing confirmation, then inspect the actual connection state.
|
|
356
|
-
Applied credentials do not by themselves prove that TURN is reachable or in use.
|
|
357
|
-
|
|
358
|
-
The modal accepts `{ "iceServers": [...] }` and a full Cloudflare envelope such as
|
|
359
|
-
`{ "success": true, "result": { "iceServers": [...] } }`. It rejects invalid JSON,
|
|
360
|
-
STUN-only configurations, invalid ICE URLs, missing TURN passwords, known expired
|
|
361
|
-
credentials, and API-token fields. Limits: 6,000 characters/UTF-8 bytes, eight ICE
|
|
362
|
-
servers and sixteen URLs. Only `stun:`, `stuns:`, `turn:` and `turns:` URLs are allowed.
|
|
363
|
-
|
|
364
|
-
**Never paste the permanent Cloudflare TURN API token.** Generate temporary
|
|
365
|
-
credentials externally in your terminal. For example, with your credentials already
|
|
366
|
-
set in your terminal environment (not in React), this copies only the temporary
|
|
367
|
-
response to the macOS clipboard:
|
|
378
|
+
- Simultaneous 1 vs 1 combat. Invitation and acceptance use the chat (`LIRA_GAME_V1:`); WebRTC signaling
|
|
379
|
+
(SDP/ICE) travels as `LIRA_WEBRTC_V1:` chat messages, at most 10,000 characters. Movement and combat use two
|
|
380
|
+
DataChannels; the inviting player resolves damage.
|
|
381
|
+
- **The chat backend (for example Django) is only the signaling relay.** It needs no WebRTC endpoint or
|
|
382
|
+
protocol change and never carries game traffic, which goes peer to peer or through TURN.
|
|
383
|
+
- Without `webrtc`, `DEFAULT_ICE_SERVERS` (public Google and Cloudflare STUN) is used. Never ship permanent TURN
|
|
384
|
+
credentials.
|
|
385
|
+
- Controls: WASD/arrows, mouse and click/Space on desktop; joystick, touch aiming and fire button on mobile.
|
|
386
|
+
Minimizing keeps the match; reloading does not recover it. Shares the tank sounds and game mute/volume.
|
|
387
|
+
|
|
388
|
+
#### Manual TURN recovery
|
|
389
|
+
|
|
390
|
+
When a direct connection fails, either player can open **Configurar conexión alternativa** (also suggested
|
|
391
|
+
after a slow or failed negotiation), paste temporary ICE credentials and choose **Aplicar y reconectar**. The
|
|
392
|
+
other player receives and applies them automatically. Settings last for the current match only.
|
|
393
|
+
|
|
394
|
+
- Accepted JSON: `{ "iceServers": [...] }` or a Cloudflare envelope `{ "success": true, "result": { "iceServers": [...] } }`,
|
|
395
|
+
optionally with `expiresAt` (ISO or Unix ms) or `expiresIn`/`ttl` (remaining seconds). The optional
|
|
396
|
+
**Validez restante en segundos** field is remaining validity, not the original TTL.
|
|
397
|
+
- Rejected: invalid JSON, STUN-only sets, invalid ICE URLs (only `stun:`, `stuns:`, `turn:`, `turns:`), TURN
|
|
398
|
+
without password, known-expired credentials and API-token fields. Limits: 6,000 characters/bytes, 8 servers,
|
|
399
|
+
16 URLs.
|
|
400
|
+
- Credentials are encrypted with an ephemeral ECDH/AES-GCM exchange before entering the chat; no plaintext
|
|
401
|
+
password is sent. Requires HTTPS (or localhost) and Web Crypto; without it, sharing fails safely. This relies
|
|
402
|
+
on the authenticated private-room relay and does not protect against a malicious signaling server.
|
|
403
|
+
- **Never paste a permanent TURN API token.** Generate temporary credentials outside the app, for example
|
|
404
|
+
(credentials in your shell environment; copies the response to the macOS clipboard):
|
|
368
405
|
|
|
369
406
|
```sh
|
|
370
407
|
curl --fail --silent --show-error \
|
|
@@ -374,59 +411,114 @@ curl --fail --silent --show-error \
|
|
|
374
411
|
--data '{"ttl":3600}' | pbcopy
|
|
375
412
|
```
|
|
376
413
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
`PLAYWRIGHT_EXECUTABLE_PATH`
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
414
|
+
- **Diagnóstico de conexión** shows SDP/ICE/DataChannel state, reconnects, RTT and sanitized errors, without
|
|
415
|
+
passwords, usernames, SDP or private addresses. `turnConfigured` only means a TURN URL is set;
|
|
416
|
+
`selectedPair.relay: true` means traffic actually uses TURN. **Forzar relay para diagnóstico** makes both
|
|
417
|
+
peers use `iceTransportPolicy: "relay"` to prove TURN works; turn it off for normal play.
|
|
418
|
+
- Real-network behavior (Wi-Fi vs cellular, restrictive firewalls) needs testing on real devices; automated
|
|
419
|
+
tests use fake RTC/TURN.
|
|
420
|
+
|
|
421
|
+
### 3D and `three`
|
|
422
|
+
|
|
423
|
+
| Scene | Loaded | Without `three`/WebGL |
|
|
424
|
+
|---|---|---|
|
|
425
|
+
| Batalla Naval board and preview | When the board is shown / preview opened | 2D grid (preview needs WebGL) |
|
|
426
|
+
| Tanques de guerra | When the match is shown | Canvas 2D |
|
|
427
|
+
| Tanques LIVE | When the match starts | Canvas 2D |
|
|
428
|
+
| Zumbido, Confeti, Avalancha | When the effect plays | Canvas 2D (Zumbido) or CSS |
|
|
429
|
+
|
|
430
|
+
`three` never enters the main bundle; each scene is a separate chunk.
|
|
431
|
+
|
|
432
|
+
## Wire protocol
|
|
433
|
+
|
|
434
|
+
Everything travels over the chat transport. Special payloads are ordinary `CHAT_MESSAGE` text with a prefix,
|
|
435
|
+
so the server only has to relay text unchanged, stamped with the sender's `user_id`.
|
|
436
|
+
|
|
437
|
+
| Prefix | Purpose | Shown in history |
|
|
438
|
+
|---|---|---|
|
|
439
|
+
| `LIRA_EDIT_V1:` / `LIRA_REACT_V1:` | Edit / reaction on an earlier message | No (applied to the target) |
|
|
440
|
+
| `sticker:` | Sticker attachment name | As a sticker |
|
|
441
|
+
| `LIRA_GAME_V1:` | Game events (private rooms only) | No (game card) |
|
|
442
|
+
| `LIRA_WEBRTC_V1:` | WebRTC signaling, including encrypted manual TURN | No, never counted as unread |
|
|
443
|
+
| `[minicode-effect:v1]` / `[minicode-countdown:v1]` | Effects / countdowns | As effect cards |
|
|
444
|
+
|
|
445
|
+
Requests carry a `request_id` and resolve on `CHAT_ACK` (or reject on a correlated `ERROR`). The frozen
|
|
446
|
+
protocol is covered by `test/contract/`.
|
|
447
|
+
|
|
448
|
+
## Local development
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
npm install
|
|
452
|
+
npm run dev # predev regenerates the demo WAV samples, then Vite opens the demo
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
The demo (`demo/`, not published) runs the library source with simulated users and in-memory messages: automatic
|
|
456
|
+
replies, groups, emoji, custom emoji uploader, reactions, editing, attachments, voice notes, light/dark theme and
|
|
457
|
+
**Nuevas funciones** (simulate incoming effects, playback settings, audio lab). No login or backend is needed.
|
|
458
|
+
|
|
459
|
+
- **🪖 Tanques con Ana:** turn-based Tanques against a demo bot. Conecta 4 and Batalla Naval have no demo opponent.
|
|
460
|
+
- **Tanques LIVE:** `npm run dev -- --host`, then open `/tanks-live-lab.html?user=ana` and `?user=luis` before
|
|
461
|
+
inviting (a dev-only Vite relay plays the chat server).
|
|
462
|
+
- Labs: `/webrtc-lab.html`, `/tanks-lab.html`, `/avalanche-lab.html`.
|
|
463
|
+
- GIF/sticker search needs your own KLIPY key (`configureKlipy`); the demo does not configure one.
|
|
464
|
+
- Browser QA for manual TURN: `scripts/check-manual-turn-browser.mjs` (needs Playwright installed separately;
|
|
465
|
+
`CHAT_DEMO_URL`, `PLAYWRIGHT_MODULE`, `PLAYWRIGHT_EXECUTABLE_PATH`). It never uses real TURN credentials.
|
|
466
|
+
- Tests use Node's runner, for example `node --test test/effects/*.test.mjs test/unit/*.test.mjs`. See
|
|
467
|
+
`docs/desarrollo/demo-y-pruebas.md` for every suite.
|
|
468
|
+
|
|
469
|
+
### Audio audition lab (demo only)
|
|
470
|
+
|
|
471
|
+
**Nuevas funciones → Audios de los efectos** plays procedurally generated WAVs next to the shipped MP3
|
|
472
|
+
(**Escuchar audio integrado**). `predev` runs `npm run audio:samples`, which writes them to `demo/audio-samples/`
|
|
473
|
+
(git-ignored, not published) from the generators in `demo/audio-lab/`:
|
|
474
|
+
|
|
475
|
+
- `pig-disco.wav` (~5 s, 104 BPM): an alternative proposal; the shipped pig is `cerdito_disco_original.mp3`.
|
|
476
|
+
- `nudge.wav` (0.9 s) and `confetti.wav` (2.7 s): sources of the earlier short cues only. The current ~7 s
|
|
477
|
+
`nudge.mp3` and `confetti.mp3` are separate recordings and do not come from these WAVs.
|
|
478
|
+
- Avalancha has no WAV; `avalanche.mp3` is its approved ~7 s recording.
|
|
479
|
+
|
|
480
|
+
The lab's own text still calls the nudge and confetti WAVs the sources of the current recordings; that is out of
|
|
481
|
+
date. Details: `docs/funciones/efectos.md` (section "Audio").
|
|
482
|
+
|
|
483
|
+
### Build, pack and source mode
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
npm run build # ESM library, lazy chunks and dist/styles.css
|
|
487
|
+
npm pack # builds again via prepack and creates minicode-dev-chat-<version>.tgz
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Install the tarball in a React app to test (`npm install /path/to/minicode-chat/minicode-dev-chat-<version>.tgz`)
|
|
491
|
+
and repeat after each change. The custom `@minicode-dev/source` export condition points at `src/`; tooling must
|
|
492
|
+
enable it explicitly (for example Vite `resolve.conditions`) and React and the other peers must resolve to one copy.
|
|
493
|
+
|
|
494
|
+
## Publishing updates
|
|
495
|
+
|
|
496
|
+
Log in with `npm login`, then:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
npm run release # patch version, build, validate contents, publish
|
|
500
|
+
npm run release -- minor # minor version
|
|
501
|
+
npm run release -- major # major version
|
|
502
|
+
npm run release -- --dry-run # build and inspect; no version change or publication
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The script checks your npm session, builds, bumps the version without a Git commit or tag, and validates the exact
|
|
506
|
+
archive (only `dist/`, `assets/`, `index.d.ts`, `README.md`, `CHANGELOG.md` and `package.json`) before
|
|
507
|
+
publishing. Temporary archives and root `.tgz` files are removed even on failure; `dist/` is kept. If publication
|
|
508
|
+
fails after the bump, the new version stays in `package.json` and the lockfile. No Git push is performed. The
|
|
509
|
+
script is called `release` because npm reserves `publish` as a lifecycle hook.
|
|
510
|
+
|
|
511
|
+
## Documentation
|
|
512
|
+
|
|
513
|
+
Internal docs (Spanish) live in the source repository and are not published to npm:
|
|
514
|
+
|
|
515
|
+
- [docs/README.md](docs/README.md): index.
|
|
516
|
+
- [docs/contexto-para-ia.md](docs/contexto-para-ia.md): rules, status, code map, protocols and pending work.
|
|
517
|
+
- Features: [docs/funciones/README.md](docs/funciones/README.md), [efectos.md](docs/funciones/efectos.md),
|
|
518
|
+
[emojis-personalizados.md](docs/funciones/emojis-personalizados.md), [youtube.md](docs/funciones/youtube.md).
|
|
519
|
+
- Games: [docs/juegos/README.md](docs/juegos/README.md),
|
|
520
|
+
[Tanques LIVE progress](docs/juegos/tanques-live/progreso-webrtc.md).
|
|
521
|
+
- Development: [demo-y-pruebas.md](docs/desarrollo/demo-y-pruebas.md),
|
|
522
|
+
[integracion-consumidor.md](docs/desarrollo/integracion-consumidor.md),
|
|
523
|
+
[publicacion.md](docs/desarrollo/publicacion.md).
|
|
524
|
+
- Changes per version: [CHANGELOG.md](CHANGELOG.md).
|