@jchat/addon-sdk 1.0.0-alpha.2 → 1.0.0-alpha.20
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 -0
- package/LICENSE +18 -196
- package/README.md +385 -20
- package/dist/cli.d.mts +35 -0
- package/dist/cli.mjs +136 -0
- package/dist/contracts.d.mts +20 -29
- package/dist/contracts.mjs +38 -0
- package/dist/diagnostics.d.mts +55 -9
- package/dist/diagnostics.mjs +66 -1
- package/dist/i18n-validator.d.mts +17 -0
- package/dist/i18n-validator.mjs +90 -0
- package/dist/i18n.mjs +191 -0
- package/dist/icon.d.mts +4 -0
- package/dist/icon.mjs +4 -0
- package/dist/index.d.mts +16 -253
- package/dist/index.mjs +106 -43
- package/dist/index2.d.mts +478 -0
- package/dist/state.d.mts +1 -0
- package/dist/state.mjs +2 -0
- package/dist/types.d.mts +12 -0
- package/dist/ui.d.mts +1 -0
- package/dist/ui.mjs +2 -0
- package/dist/vite.d.mts +64 -10
- package/dist/vite.mjs +222 -32
- package/package.json +119 -17
- package/skills/create-jchat-addon/SKILL.md +140 -0
- package/skills/jchat-addon-development/SKILL.md +260 -0
- package/tsconfig.addon.json +20 -0
- package/src/contracts.d.ts +0 -127
- package/src/contracts.ts +0 -132
- package/src/diagnostics.d.ts +0 -19
- package/src/diagnostics.test.ts +0 -106
- package/src/diagnostics.ts +0 -182
- package/src/index.d.ts +0 -249
- package/src/index.test.ts +0 -35
- package/src/index.ts +0 -299
- package/src/vite.test.ts +0 -62
- package/src/vite.ts +0 -289
package/README.md
CHANGED
|
@@ -1,45 +1,410 @@
|
|
|
1
1
|
# @jchat/addon-sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for
|
|
3
|
+
Official TypeScript SDK for developing extensions, UI widgets, and server plugins for the [JChat](https://jchatlive.com) communication platform.
|
|
4
4
|
|
|
5
|
-
Provides
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Provides typed interfaces, manifest contracts, Vite bundler plugins, and CLI development tooling for building sandboxed JChat addons.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@jchat/addon-sdk)
|
|
8
|
+
[](https://jchatlive.com/terms)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Architecture Overview
|
|
13
|
+
|
|
14
|
+
JChat addons are **sandboxed, modular extensions** designed to enhance real-time chats without modifying core database schemas or server code.
|
|
15
|
+
|
|
16
|
+
- **Ultra-Lightweight Server Bundles**: Elysia and TypeBox are externalized and provided directly by the host server runtime via `ctx.t` and `app`. Server bundles (`build/api.js`) compile down to just **2–7 KB**.
|
|
17
|
+
- **Zero Frontend Bundle Bloat**: Host externals (`react`, `react-dom`, `@jchat/ui`, `@jchat/icon`, `@tanstack/react-query`, `jotai`) are injected at runtime by the JChat host application.
|
|
18
|
+
- **Isolated Database Schema**: Addon tables reside exclusively in the isolated `addons` PostgreSQL schema (e.g. `addons.GlimpseStories`), preventing collisions with core chat tables.
|
|
19
|
+
- **Scoped Cache & Real-Time Events**: Addons receive namespaced Redis cache (`addon:<id>:<key>`) and real-time WebSocket event broadcasting scoped to sites, rooms, or users.
|
|
20
|
+
- **20-Language i18n Standard**: First-class internationalization with 100% parity across all 20 supported host languages, including full RTL direction support.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- **Bun**: `1.4.0+`
|
|
27
|
+
- **React**: `19+`
|
|
28
|
+
- **Vite**: `8+`
|
|
29
|
+
|
|
30
|
+
---
|
|
8
31
|
|
|
9
32
|
## Installation
|
|
10
33
|
|
|
34
|
+
Install as a development dependency:
|
|
35
|
+
|
|
11
36
|
```bash
|
|
12
|
-
|
|
13
|
-
# or
|
|
14
|
-
bun add @jchat/addon-sdk
|
|
37
|
+
bun add -d @jchat/addon-sdk
|
|
15
38
|
```
|
|
16
39
|
|
|
17
|
-
|
|
40
|
+
Or scaffold a new addon instantly with the official CLI:
|
|
18
41
|
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
```bash
|
|
43
|
+
bun create @jchat/addon my-new-addon
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
21
47
|
|
|
22
|
-
##
|
|
48
|
+
## Quick Start Guide
|
|
49
|
+
|
|
50
|
+
### 1. Define the Addon Manifest (`src/manifest.ts`)
|
|
51
|
+
|
|
52
|
+
Use `defineAddonManifest` (or type with `AddonManifest`) to declare metadata, permissions, mount points, and supported languages:
|
|
23
53
|
|
|
24
54
|
```ts
|
|
25
|
-
import
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
55
|
+
import { defineAddonManifest } from "@jchat/addon-sdk"
|
|
56
|
+
|
|
57
|
+
export default defineAddonManifest({
|
|
58
|
+
id: "gifyard",
|
|
59
|
+
name: "Gifyard GIF Picker",
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
description: "Search and insert animated GIFs directly into chat messages.",
|
|
62
|
+
author: "JChat Ecosystem Team",
|
|
63
|
+
publisher: { id: "jchat" },
|
|
64
|
+
compatibility: {
|
|
65
|
+
jchat: ">=1.0.0-alpha.0 <2.0.0",
|
|
66
|
+
addonSdk: "^1.0.0"
|
|
67
|
+
},
|
|
68
|
+
locales: [
|
|
69
|
+
"ar",
|
|
70
|
+
"bg",
|
|
71
|
+
"de",
|
|
72
|
+
"el",
|
|
73
|
+
"en",
|
|
74
|
+
"es",
|
|
75
|
+
"fr",
|
|
76
|
+
"he",
|
|
77
|
+
"hi",
|
|
78
|
+
"hr",
|
|
79
|
+
"kn",
|
|
80
|
+
"ml",
|
|
81
|
+
"nl",
|
|
82
|
+
"pt",
|
|
83
|
+
"ro",
|
|
84
|
+
"ru",
|
|
85
|
+
"ta",
|
|
86
|
+
"te",
|
|
87
|
+
"tr",
|
|
88
|
+
"ur"
|
|
89
|
+
],
|
|
90
|
+
defaultLocale: "en",
|
|
91
|
+
frontend: {
|
|
92
|
+
bundle: "./ui.js",
|
|
93
|
+
mountPoints: ["chat-toolbar", "emoji-picker-gif-tab", "addon-settings"]
|
|
94
|
+
},
|
|
95
|
+
server: {
|
|
96
|
+
bundle: "./api.ts",
|
|
97
|
+
migrations: ["./migrations/0001_init.sql"]
|
|
98
|
+
},
|
|
99
|
+
accessPermission: "addon.gifyard.view",
|
|
100
|
+
permissions: ["addon.gifyard.view", "addon.gifyard.manage"],
|
|
101
|
+
permissionDefinitions: [
|
|
102
|
+
{
|
|
103
|
+
name: "addon.gifyard.view",
|
|
104
|
+
label: "View GIFs",
|
|
105
|
+
description: "Search and preview animated GIFs in chat"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "addon.gifyard.manage",
|
|
109
|
+
label: "Manage Gifyard",
|
|
110
|
+
description: "Configure Giphy API keys and content rating settings"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
})
|
|
31
114
|
```
|
|
32
115
|
|
|
33
|
-
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 2. Build the Frontend UI (`src/ui.tsx`)
|
|
119
|
+
|
|
120
|
+
Addon UI components receive typed `AddonProps` and access host services via the `useAddonSDK()` hook:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useAddonSDK, useAddonQuery, useTranslation, type AddonProps } from "@jchat/addon-sdk"
|
|
124
|
+
import Icons from "@jchat/addon-sdk/icon"
|
|
125
|
+
import locales from "./locales"
|
|
126
|
+
|
|
127
|
+
export default function MyAddon({ mountPoint, roomId }: AddonProps) {
|
|
128
|
+
const sdk = useAddonSDK()
|
|
129
|
+
const { t, dir } = useTranslation(locales)
|
|
130
|
+
|
|
131
|
+
// Type-safe data fetching via TanStack Query bridge
|
|
132
|
+
const { data, isLoading } = useAddonQuery<{ items: string[] }>({
|
|
133
|
+
path: "/items",
|
|
134
|
+
queryKey: [roomId]
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const handleInsert = (text: string) => {
|
|
138
|
+
// Insert rich content directly into the active Slate message editor
|
|
139
|
+
sdk.editor.dispatchCommand("INSERT_TEXT", text)
|
|
140
|
+
sdk.toast(t("item_inserted"), "success")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div dir={dir} className="rounded-2xl border border-border bg-card p-4 shadow-sm">
|
|
145
|
+
<div className="flex items-center gap-2 font-bold">
|
|
146
|
+
<Icons.Sparkles className="size-4 text-primary" />
|
|
147
|
+
<span>{t("addon_title")}</span>
|
|
148
|
+
</div>
|
|
149
|
+
{isLoading ? (
|
|
150
|
+
<p className="text-xs text-muted-foreground">{t("loading")}</p>
|
|
151
|
+
) : (
|
|
152
|
+
<button
|
|
153
|
+
onClick={() => handleInsert("Hello from Addon!")}
|
|
154
|
+
className="mt-3 rounded-lg bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground"
|
|
155
|
+
>
|
|
156
|
+
{t("insert_action")}
|
|
157
|
+
</button>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> **Directional Styling Rule**: Always use Tailwind logical directional properties (`ps-*`, `pe-*`, `ms-*`, `me-*`, `text-start`, `text-end`, `start-*`, `end-*`) to automatically adapt to RTL languages (`ar`, `he`, `ur`).
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### 3. Build the Backend API (`src/api.ts`)
|
|
169
|
+
|
|
170
|
+
Define type-safe Elysia API endpoints. The schema validator `t` is injected directly via `ctx.t`, so your production bundle never imports or bundles Elysia:
|
|
34
171
|
|
|
35
172
|
```ts
|
|
173
|
+
import type { AddonApp, AddonServerContext } from "@jchat/addon-sdk"
|
|
174
|
+
|
|
175
|
+
export interface MyAddonDatabase {
|
|
176
|
+
addon_items: {
|
|
177
|
+
id: string
|
|
178
|
+
siteId: string
|
|
179
|
+
roomId: string
|
|
180
|
+
userId: string
|
|
181
|
+
title: string
|
|
182
|
+
createdAt: Date
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default (
|
|
187
|
+
{ db, t, emit, cache, getConfig, getUser, hasPermission }: AddonServerContext<MyAddonDatabase>,
|
|
188
|
+
app: AddonApp
|
|
189
|
+
) => {
|
|
190
|
+
return app
|
|
191
|
+
.get(
|
|
192
|
+
"/items",
|
|
193
|
+
async ({ query, user }) => {
|
|
194
|
+
const siteId = user?.siteId ?? ""
|
|
195
|
+
const items = await db
|
|
196
|
+
.selectFrom("addon_items")
|
|
197
|
+
.where("siteId", "=", siteId)
|
|
198
|
+
.where("roomId", "=", query.roomId ?? "")
|
|
199
|
+
.orderBy("createdAt", "desc")
|
|
200
|
+
.limit(20)
|
|
201
|
+
.selectAll()
|
|
202
|
+
.execute()
|
|
203
|
+
|
|
204
|
+
return { items }
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
requirePermission: "addon.myaddon.view",
|
|
208
|
+
query: t.Object({
|
|
209
|
+
roomId: t.Optional(t.String())
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
.post(
|
|
214
|
+
"/items",
|
|
215
|
+
async ({ body, user, status }) => {
|
|
216
|
+
if (!user) {
|
|
217
|
+
return status(401, { error: "Authentication required" })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const id = Bun.randomUUIDv7()
|
|
221
|
+
await db
|
|
222
|
+
.insertInto("addon_items")
|
|
223
|
+
.values({
|
|
224
|
+
id,
|
|
225
|
+
siteId: user.siteId!,
|
|
226
|
+
roomId: body.roomId,
|
|
227
|
+
userId: user.id,
|
|
228
|
+
title: body.title,
|
|
229
|
+
createdAt: new Date()
|
|
230
|
+
})
|
|
231
|
+
.execute()
|
|
232
|
+
|
|
233
|
+
// Broadcast real-time WebSocket event
|
|
234
|
+
await emit("addon:myaddon:item-created", { id, title: body.title }, { roomId: body.roomId })
|
|
235
|
+
|
|
236
|
+
return { id, title: body.title }
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
requirePermission: "addon.myaddon.manage",
|
|
240
|
+
body: t.Object({
|
|
241
|
+
roomId: t.String(),
|
|
242
|
+
title: t.String({ minLength: 1 })
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 4. Configure Vite Bundling (`vite.config.ts`)
|
|
252
|
+
|
|
253
|
+
Include the official Vite plugin for automatic manifest bundling, externalization, and live development:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { defineConfig } from "vite"
|
|
36
257
|
import { jchatAddon } from "@jchat/addon-sdk/vite"
|
|
37
258
|
|
|
38
|
-
export default {
|
|
259
|
+
export default defineConfig({
|
|
39
260
|
plugins: [jchatAddon()]
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## SDK Modules & API Reference
|
|
267
|
+
|
|
268
|
+
### 1. Host Bridge: `useAddonSDK()`
|
|
269
|
+
|
|
270
|
+
Provides access to the active JChat host application bridge:
|
|
271
|
+
|
|
272
|
+
| Property | Type | Description |
|
|
273
|
+
| :--------------- | :----------------- | :------------------------------------------------------------------------------------------------------------- |
|
|
274
|
+
| `api` | `AddonApi` | Authenticated HTTP client (`get`, `post`, `put`, `delete`) automatically scoped to `/api/v1/addons/<addonId>`. |
|
|
275
|
+
| `settings` | `AddonSettingsApi` | First-class configuration helper to load and save addon settings (`get()`, `save()`). |
|
|
276
|
+
| `editor` | `AddonEditorApi` | Active Slate editor controls (`dispatchCommand`, `insertNode`, `addAttachment`, `focus`). |
|
|
277
|
+
| `events` | `AddonEventBus` | Real-time WebSocket event listeners (`on`, `off`). |
|
|
278
|
+
| `toast` | `function` | Display host toast notifications (`default`, `success`, `error`). |
|
|
279
|
+
| `navigate` | `function` | Navigate to internal host routes without triggering a full page reload. |
|
|
280
|
+
| `supportedFonts` | `AddonFont[]` | List of fonts configured by the site administrator with scale multipliers. |
|
|
281
|
+
| `locale` | `string` | Active user locale code (e.g. `'en'`, `'ta'`, `'ar'`). |
|
|
282
|
+
| `dir` | `'ltr' \| 'rtl'` | Current layout text direction. |
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### 2. Data Fetching: `@jchat/addon-sdk/query`
|
|
287
|
+
|
|
288
|
+
Integrated TanStack Query wrappers pre-configured with host caching, auto-invalidation, and credentials:
|
|
289
|
+
|
|
290
|
+
- **`useAddonQuery({ path, queryKey, ...options })`**: Cached GET requests automatically prefixed with `/api/v1/addons/<addonId>`.
|
|
291
|
+
- **`useAddonMutation({ path, method, invalidateQueries, ...options })`**: Declarative mutations with automatic query invalidation.
|
|
292
|
+
- **`useAddonInfiniteQuery({ path, queryKey, initialPageParam, getNextPageParam })`**: Paginated and infinite-scroll queries.
|
|
293
|
+
- **`useQueryClient()`**: Access the underlying TanStack Query client.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### 3. Atomic State: `@jchat/addon-sdk/state`
|
|
298
|
+
|
|
299
|
+
Re-exports Jotai state management primitives for maintaining synchronized, reactive UI state across multiple mount points:
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { atom, useAtom, useAtomValue, useSetAtom } from "@jchat/addon-sdk/state"
|
|
303
|
+
|
|
304
|
+
export const activeTabAtom = atom<"trending" | "search">("trending")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### 4. Localization: `useTranslation()` & `useLocale()`
|
|
310
|
+
|
|
311
|
+
Addons must support all **20 host languages**:
|
|
312
|
+
`ar`, `bg`, `de`, `el`, `en`, `es`, `fr`, `he`, `hi`, `hr`, `kn`, `ml`, `nl`, `pt`, `ro`, `ru`, `ta`, `te`, `tr`, `ur`.
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { useTranslation, useLocale } from "@jchat/addon-sdk"
|
|
316
|
+
import locales from "./locales"
|
|
317
|
+
|
|
318
|
+
export default function MyComponent() {
|
|
319
|
+
const { t, dir, isRtl, locale } = useTranslation(locales)
|
|
320
|
+
return <div dir={dir}>{t("welcome", { name: "User" })}</div>
|
|
40
321
|
}
|
|
41
322
|
```
|
|
42
323
|
|
|
324
|
+
For backend server routes (`src/api.ts`), use `createTranslator(locales, defaultLocale)` to translate server-sent notifications or messages.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### 5. Icons: `@jchat/addon-sdk/icon`
|
|
329
|
+
|
|
330
|
+
Imports the site's active icon pack directly, ensuring visual consistency with the host theme:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
import Icons from "@jchat/addon-sdk/icon"
|
|
334
|
+
|
|
335
|
+
;<Icons.Heart className="size-4 text-red-500" />
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
### 6. Server Context: `AddonServerContext`
|
|
341
|
+
|
|
342
|
+
The context passed to your server plugin (`src/api.ts`):
|
|
343
|
+
|
|
344
|
+
| Property | Type | Description |
|
|
345
|
+
| :-------------- | :--------------------- | :---------------------------------------------------------------------------------------- |
|
|
346
|
+
| `t` | `typeof t` | Elysia / TypeBox schema validator for defining `body`, `query`, `params`, and `headers`. |
|
|
347
|
+
| `db` | `AddonDatabase<T>` | Schema-scoped Kysely query builder (`selectFrom`, `insertInto`, `updateTable`, etc.). |
|
|
348
|
+
| `cache` | `AddonCacheClient` | Namespaced Redis client (`get`, `set`, `del`, `incr`, `expire`, `ttl`). |
|
|
349
|
+
| `emit` | `function` | Real-time WebSocket event broadcaster with target options (`siteId`, `roomId`, `userId`). |
|
|
350
|
+
| `getConfig` | `(siteId) => Promise` | Retrieve encrypted site configuration with automatic decryption. |
|
|
351
|
+
| `getUser` | `(userId) => Promise` | Fetch a single user's public profile (`id`, `name`, `displayName`, `avatar`, `gender`). |
|
|
352
|
+
| `getUsers` | `(userIds) => Promise` | Batch-fetch multiple user profiles in a single query. |
|
|
353
|
+
| `hasPermission` | `(user, perm) => bool` | Check whether a request user holds a given permission scope. |
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Supported UI Mount Points
|
|
358
|
+
|
|
359
|
+
Addons can mount into any combination of locations defined in `MOUNT_POINTS`:
|
|
360
|
+
|
|
361
|
+
| Mount Point | Identifier | Description |
|
|
362
|
+
| :----------------------- | :------------------------- | :------------------------------------------------------------------------------ |
|
|
363
|
+
| **Chat Toolbar** | `chat-toolbar` | Action button rendered in the message composer toolbar. |
|
|
364
|
+
| **Chat Plus Menu** | `chat-plus-menu` | Action item inside the message composer (+) expandable menu. |
|
|
365
|
+
| **Chat Sidebar** | `chat-sidebar` | Dedicated right-hand collapsible panel in active chat rooms. |
|
|
366
|
+
| **Chat Header** | `chat-header` | Action button or widget rendered in the chat room top header. |
|
|
367
|
+
| **Sidebar Top** | `sidebar-top` | Placed at the top of the left-hand navigation sidebar. |
|
|
368
|
+
| **Sidebar Bottom** | `sidebar-bottom` | Placed at the bottom of the left-hand navigation sidebar (e.g. AdSense banner). |
|
|
369
|
+
| **User Popover Actions** | `user-popover-actions` | Custom action buttons in the user profile card popup. |
|
|
370
|
+
| **Message Context Menu** | `message-context-menu` | Custom actions on right-clicking or long-pressing a message. |
|
|
371
|
+
| **Message Feed Overlay** | `message-feed-overlay` | Floating banner or widget overlaid across the message stream. |
|
|
372
|
+
| **GIF Picker Tab** | `emoji-picker-gif-tab` | Custom GIF provider tab in the emoji/media drawer. |
|
|
373
|
+
| **Sticker Picker Tab** | `emoji-picker-sticker-tab` | Custom sticker provider tab in the emoji/media drawer. |
|
|
374
|
+
| **Addon Settings** | `addon-settings` | Configuration panel inside the Site Admin Dashboard. |
|
|
375
|
+
| **Owner Settings** | `owner-settings` | Global configuration panel inside the System Owner Dashboard. |
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## CLI Development Workflow
|
|
380
|
+
|
|
381
|
+
The SDK provides the `jchat-addon` CLI for local development, building, and validation:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Start live development watcher with hot module reloading
|
|
385
|
+
bun run dev
|
|
386
|
+
|
|
387
|
+
# Run TypeScript typechecks
|
|
388
|
+
bun run typecheck
|
|
389
|
+
|
|
390
|
+
# Execute local unit and contract tests
|
|
391
|
+
bun test
|
|
392
|
+
|
|
393
|
+
# Build production bundles (build/ui.js and build/api.js)
|
|
394
|
+
bun run build
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Publishing to JChat Marketplace
|
|
400
|
+
|
|
401
|
+
1. Build your addon package: `bun run build`.
|
|
402
|
+
2. Ensure all 20 translation files pass validation: `bun test`.
|
|
403
|
+
3. Package your addon distribution archive.
|
|
404
|
+
4. Sign in to the **[JChat Store Developer Portal](https://jchatlive.com/addons)** and submit your addon package for certified distribution.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
43
408
|
## License
|
|
44
409
|
|
|
45
|
-
|
|
410
|
+
Proprietary — All rights reserved. See the [Commercial EULA & Licensing Terms](https://jchatlive.com/terms) for full details.
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AddonManifest } from "./contracts.mjs";
|
|
2
|
+
import { InlineConfig } from "vite";
|
|
3
|
+
//#region src/cli.d.ts
|
|
4
|
+
/** Options for resolving addon build configuration. */
|
|
5
|
+
export interface AddonCliOptions {
|
|
6
|
+
rootDir?: string;
|
|
7
|
+
command?: "build" | "dev" | "help";
|
|
8
|
+
mode?: "production" | "development";
|
|
9
|
+
watch?: boolean;
|
|
10
|
+
zip?: boolean;
|
|
11
|
+
signingPrivateKey?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Automatically loads the addon manifest from `src/manifest.ts`, `src/manifest.js`, or
|
|
15
|
+
* `src/manifest.json`.
|
|
16
|
+
*
|
|
17
|
+
* @param rootDir - Root directory of the addon workspace.
|
|
18
|
+
* @returns Parsed {@link AddonManifest}.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadAddonManifest(rootDir: string): Promise<AddonManifest>;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the full Vite configuration for building a JChat addon, merging user custom configs if
|
|
23
|
+
* present.
|
|
24
|
+
*
|
|
25
|
+
* @param options - CLI build options.
|
|
26
|
+
* @returns Fully merged {@link InlineConfig} for Vite.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveAddonBuildConfig(options?: AddonCliOptions): Promise<InlineConfig>;
|
|
29
|
+
/**
|
|
30
|
+
* Main CLI handler for `jchat-addon`.
|
|
31
|
+
*
|
|
32
|
+
* @param args - CLI arguments (defaults to `process.argv.slice(2)`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function runAddonCli(args?: string[]): Promise<void>;
|
|
35
|
+
//#endregion
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { jchatAddon } from "./vite.mjs";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { build, loadConfigFromFile, mergeConfig } from "vite";
|
|
7
|
+
//#region src/cli.ts
|
|
8
|
+
function hasPlugin(plugins, name) {
|
|
9
|
+
if (!plugins) return false;
|
|
10
|
+
if (Array.isArray(plugins)) return plugins.some((p) => hasPlugin(p, name));
|
|
11
|
+
if (typeof plugins === "object" && plugins !== null && "name" in plugins && plugins.name === name) return true;
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Automatically loads the addon manifest from `src/manifest.ts`, `src/manifest.js`, or
|
|
16
|
+
* `src/manifest.json`.
|
|
17
|
+
*
|
|
18
|
+
* @param rootDir - Root directory of the addon workspace.
|
|
19
|
+
* @returns Parsed {@link AddonManifest}.
|
|
20
|
+
*/
|
|
21
|
+
async function loadAddonManifest(rootDir) {
|
|
22
|
+
const existingPath = [
|
|
23
|
+
path.resolve(rootDir, "src/manifest.ts"),
|
|
24
|
+
path.resolve(rootDir, "src/manifest.js"),
|
|
25
|
+
path.resolve(rootDir, "src/manifest.mjs"),
|
|
26
|
+
path.resolve(rootDir, "src/manifest.json")
|
|
27
|
+
].find((filePath) => fs.existsSync(filePath));
|
|
28
|
+
if (existingPath) {
|
|
29
|
+
if (existingPath.endsWith(".json")) {
|
|
30
|
+
const text = fs.readFileSync(existingPath, "utf-8");
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
}
|
|
33
|
+
const imported = await import(pathToFileURL(existingPath).href);
|
|
34
|
+
return imported.default || imported.manifest;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Could not find addon manifest in ${rootDir}/src (expected manifest.ts or manifest.json)`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolves the full Vite configuration for building a JChat addon, merging user custom configs if
|
|
40
|
+
* present.
|
|
41
|
+
*
|
|
42
|
+
* @param options - CLI build options.
|
|
43
|
+
* @returns Fully merged {@link InlineConfig} for Vite.
|
|
44
|
+
*/
|
|
45
|
+
async function resolveAddonBuildConfig(options = {}) {
|
|
46
|
+
const rootDir = path.resolve(options.rootDir || process.cwd());
|
|
47
|
+
const isDev = options.command === "dev" || options.mode === "development";
|
|
48
|
+
const mode = options.mode || (isDev ? "development" : "production");
|
|
49
|
+
const watch = options.watch ?? options.command === "dev";
|
|
50
|
+
const manifest = await loadAddonManifest(rootDir);
|
|
51
|
+
const baseConfig = {
|
|
52
|
+
root: rootDir,
|
|
53
|
+
configFile: false,
|
|
54
|
+
mode,
|
|
55
|
+
plugins: [tailwindcss(), jchatAddon({
|
|
56
|
+
manifest,
|
|
57
|
+
development: isDev,
|
|
58
|
+
zip: options.zip,
|
|
59
|
+
signingPrivateKey: options.signingPrivateKey
|
|
60
|
+
})],
|
|
61
|
+
build: { watch: watch ? {} : null }
|
|
62
|
+
};
|
|
63
|
+
const customConfigPath = [
|
|
64
|
+
"vite.config.ts",
|
|
65
|
+
"vite.config.js",
|
|
66
|
+
"vite.config.mjs",
|
|
67
|
+
"vite.config.cjs"
|
|
68
|
+
].map((file) => path.resolve(rootDir, file)).find((file) => fs.existsSync(file));
|
|
69
|
+
if (customConfigPath) {
|
|
70
|
+
const loaded = await loadConfigFromFile({
|
|
71
|
+
command: "build",
|
|
72
|
+
mode
|
|
73
|
+
}, customConfigPath, rootDir);
|
|
74
|
+
if (loaded?.config) {
|
|
75
|
+
if (hasPlugin(loaded.config.plugins, "jchat-addon")) return mergeConfig(loaded.config, {
|
|
76
|
+
root: rootDir,
|
|
77
|
+
mode,
|
|
78
|
+
build: { watch: watch ? {} : null }
|
|
79
|
+
});
|
|
80
|
+
return mergeConfig(baseConfig, loaded.config);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return baseConfig;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Main CLI handler for `jchat-addon`.
|
|
87
|
+
*
|
|
88
|
+
* @param args - CLI arguments (defaults to `process.argv.slice(2)`).
|
|
89
|
+
*/
|
|
90
|
+
async function runAddonCli(args = process.argv.slice(2)) {
|
|
91
|
+
let command = "build";
|
|
92
|
+
let watch = false;
|
|
93
|
+
let mode;
|
|
94
|
+
let zip;
|
|
95
|
+
for (const arg of args) if (arg === "dev") {
|
|
96
|
+
command = "dev";
|
|
97
|
+
watch = true;
|
|
98
|
+
mode = "development";
|
|
99
|
+
} else if (arg === "build") command = "build";
|
|
100
|
+
else if (arg === "--watch" || arg === "-w") watch = true;
|
|
101
|
+
else if (arg === "--dev") mode = "development";
|
|
102
|
+
else if (arg === "--prod" || arg === "--production") mode = "production";
|
|
103
|
+
else if (arg === "--no-zip") zip = false;
|
|
104
|
+
else if (arg === "--zip") zip = true;
|
|
105
|
+
else if (arg === "--help" || arg === "-h") command = "help";
|
|
106
|
+
if (command === "help") {
|
|
107
|
+
console.log(`
|
|
108
|
+
JChat Addon CLI (jchat-addon)
|
|
109
|
+
|
|
110
|
+
Usage:
|
|
111
|
+
jchat-addon build Build the addon for production
|
|
112
|
+
jchat-addon dev Build and watch the addon in development mode
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
--watch, -w Watch files for changes
|
|
116
|
+
--dev Run in development mode
|
|
117
|
+
--prod Run in production mode
|
|
118
|
+
--no-zip Do not package the build into a .zip archive
|
|
119
|
+
--help, -h Show this help message
|
|
120
|
+
`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const config = await resolveAddonBuildConfig({
|
|
124
|
+
command,
|
|
125
|
+
mode,
|
|
126
|
+
watch,
|
|
127
|
+
zip
|
|
128
|
+
});
|
|
129
|
+
await build(config);
|
|
130
|
+
}
|
|
131
|
+
if (import.meta.main) runAddonCli().catch((error) => {
|
|
132
|
+
console.error("❌ Addon build failed:", error);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
|
135
|
+
//#endregion
|
|
136
|
+
export { loadAddonManifest, resolveAddonBuildConfig, runAddonCli };
|
package/dist/contracts.d.mts
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
//#region src/contracts.d.ts
|
|
2
|
-
type AddonPermission = string;
|
|
3
|
-
/**
|
|
4
|
-
* Defines a single configuration field for an addon's settings panel. These are shown to site
|
|
5
|
-
* admins in the Jchat dashboard.
|
|
6
|
-
*/
|
|
7
|
-
interface ConfigSchemaField {
|
|
8
|
-
/** Unique key used to store and retrieve the value in `getConfig` */
|
|
9
|
-
key: string;
|
|
10
|
-
/** Data type for validation and UI rendering */
|
|
11
|
-
type: "string" | "number" | "boolean";
|
|
12
|
-
/** Human-readable label shown in the settings UI */
|
|
13
|
-
label: string;
|
|
14
|
-
/** If true, the admin must provide a value before saving */
|
|
15
|
-
required?: boolean;
|
|
16
|
-
/** Whether the value is sensitive (encrypted at rest and masked in the UI) */
|
|
17
|
-
secret?: boolean;
|
|
18
|
-
/** Default value pre-filled in the settings form for new installations */
|
|
19
|
-
default?: string | number | boolean;
|
|
20
|
-
}
|
|
21
2
|
/** Definition of a custom permission introduced by an addon. */
|
|
22
|
-
interface AddonPermissionDefinition {
|
|
3
|
+
export interface AddonPermissionDefinition {
|
|
23
4
|
/** Permission identifier (recommended: `addon.<id>.<action>`) */
|
|
24
5
|
name: string;
|
|
25
6
|
/** Short, human-readable name for the permission */
|
|
@@ -28,7 +9,7 @@ interface AddonPermissionDefinition {
|
|
|
28
9
|
description: string;
|
|
29
10
|
}
|
|
30
11
|
/** Valid locations where an addon can be rendered within the Jchat layout. */
|
|
31
|
-
declare const MOUNT_POINTS: {
|
|
12
|
+
export declare const MOUNT_POINTS: {
|
|
32
13
|
/** Top of the main chat column */
|
|
33
14
|
readonly CHAT_HEADER: "chat-header";
|
|
34
15
|
/** Below the main chat header, above messages */
|
|
@@ -53,16 +34,22 @@ declare const MOUNT_POINTS: {
|
|
|
53
34
|
readonly CHAT_PLUS_MENU: "chat-plus-menu";
|
|
54
35
|
/** Custom tab in the emoji/gif selection panel */
|
|
55
36
|
readonly EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab";
|
|
37
|
+
/** Custom tab in the emoji/sticker selection panel */
|
|
38
|
+
readonly EMOJI_PICKER_STICKER_TAB: "emoji-picker-sticker-tab";
|
|
56
39
|
/** Full-screen or modal dialog specifically for TubeScan */
|
|
57
40
|
readonly TUBESCAN_DIALOG: "tubescan-dialog";
|
|
41
|
+
/** Dedicated settings panel rendered in the Owner Dashboard */
|
|
42
|
+
readonly OWNER_SETTINGS: "owner-settings";
|
|
43
|
+
/** Dedicated settings panel rendered in the Site Admin Dashboard */
|
|
44
|
+
readonly ADDON_SETTINGS: "addon-settings";
|
|
58
45
|
};
|
|
59
46
|
/** Union type of all valid addon mount points. */
|
|
60
|
-
type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
|
|
47
|
+
export type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
|
|
61
48
|
/**
|
|
62
49
|
* The `manifest.json` schema required for every Jchat addon. This file describes how the addon is
|
|
63
50
|
* identified and mounted.
|
|
64
51
|
*/
|
|
65
|
-
interface AddonManifest {
|
|
52
|
+
export interface AddonManifest {
|
|
66
53
|
/** Unique kebab-case addon identifier (must match the folder name) */
|
|
67
54
|
id: string;
|
|
68
55
|
/** Human-readable addon name */
|
|
@@ -77,6 +64,13 @@ interface AddonManifest {
|
|
|
77
64
|
homepage?: string;
|
|
78
65
|
/** SPDX license identifier (e.g. `MIT`) */
|
|
79
66
|
license?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Supported language codes (e.g. ["ar", "de", "en", "es", "fr", "hi", "kn", "ml", "ta", "te",
|
|
69
|
+
* "ur"])
|
|
70
|
+
*/
|
|
71
|
+
locales?: string[];
|
|
72
|
+
/** Default fallback language code (e.g. "en") */
|
|
73
|
+
defaultLocale?: string;
|
|
80
74
|
/** Host and SDK versions this directly executed plugin was built and tested against */
|
|
81
75
|
compatibility: {
|
|
82
76
|
/** Supported JChat versions as a semantic-version range, for example `^1.0.0` */
|
|
@@ -118,13 +112,10 @@ interface AddonManifest {
|
|
|
118
112
|
* The minimum permission required to see or use this addon. If provided, the host gates
|
|
119
113
|
* visibility in the UI and access to all server routes.
|
|
120
114
|
*/
|
|
121
|
-
accessPermission?:
|
|
115
|
+
accessPermission?: string;
|
|
122
116
|
/** Full list of all permissions used by this addon for informational purposes */
|
|
123
|
-
permissions?:
|
|
117
|
+
permissions?: string[];
|
|
124
118
|
/** Custom permissions that should be registered with the host application on installation */
|
|
125
119
|
permissionDefinitions?: AddonPermissionDefinition[];
|
|
126
|
-
/** Settings form structure shown to site admins. Values are stored encrypted per site */
|
|
127
|
-
configSchema?: ConfigSchemaField[];
|
|
128
120
|
}
|
|
129
|
-
//#endregion
|
|
130
|
-
export { MOUNT_POINTS as a, ConfigSchemaField as i, AddonPermission as n, MountPoint as o, AddonPermissionDefinition as r, AddonManifest as t };
|
|
121
|
+
//#endregion
|