@jchat/addon-sdk 1.0.0-alpha.2 → 1.0.0-alpha.21

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/index.d.mts CHANGED
@@ -1,257 +1,20 @@
1
- import { a as MOUNT_POINTS, i as ConfigSchemaField, n as AddonPermission, o as MountPoint, r as AddonPermissionDefinition, t as AddonManifest } from "./contracts.mjs";
2
- import { ComponentType, SVGProps } from "react";
3
- import { Elysia } from "elysia";
4
- import { Kysely } from "kysely";
5
- //#region ../icon/src/types.d.ts
6
- type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
7
- declare const ICON_NAMES: readonly ["ProfileMen", "ProfileWomen", "Stories", "Search", "AdminPanel", "Investigation", "Logout", "Users", "Settings", "RoomList", "AdminShip", "Announcement", "NewsFeed", "Ranks", "TopPoints", "Like", "DisLike", "Love", "Funny", "Comment", "Chat", "Brush", "Image", "Mic", "More", "Palette", "Smiley", "Send", "Private", "Action", "View", "Female", "Male", "LuSend", "Heart", "Trash", "HorizontalOption", "Menu", "UserGroup", "Globe", "Plus", "Loader", "X", "UserRoundPlus", "Footprints", "Calendar", "Volume", "Youtube", "Upload", "Play", "Pause", "Stop", "Ok", "ImageFill", "MicFill", "YoutubeFill", "PlusFill", "Music", "Gift", "Camera", "Envelope", "Key", "Signature", "UserPen", "Edit", "AddressCard", "Star", "StatusOnline", "StatusAway", "StatusBusy", "StatusEating", "StatusGaming", "StatusSinging", "StatusListening", "StatusStay"];
8
- type IconName = (typeof ICON_NAMES)[number];
9
- type IconPack = Record<IconName, IconComponent>;
1
+ import { i as IconPack, n as IconComponent, r as IconName, t as ICON_NAMES } from "./types.mjs";
2
+ //#region ../icon/dist/index.d.mts
3
+ //#region src/packs/lucide.d.ts
4
+ declare const Youtube: IconComponent;
5
+ declare const lucidePack: IconPack;
10
6
  //#endregion
11
7
  //#region src/index.d.ts
12
- type AddonJsonBody = Record<string, unknown> | unknown[] | null;
13
- /**
14
- * @jchat/addon-sdk
15
- *
16
- * Lightweight SDK for Jchat addon developers. Provides TypeScript types for addon props, manifest
17
- * schema, and host API access. This package is always EXTERNALISED from addon bundles — the host
18
- * app provides the runtime implementation.
19
- */
20
- /** Minimal user profile accessible to addon backends. */
21
- interface AddonUser {
22
- /** Unique user identifier */
23
- id: string;
24
- /** Display name */
25
- name: string;
26
- /** Full display name */
27
- displayName: string;
28
- /** Full URL to the user's avatar image */
29
- avatar: string;
30
- /** User's gender identifier */
31
- gender: string;
32
- }
33
- interface AddonRequestUser {
34
- id?: string;
35
- permissions?: string[];
36
- siteId?: string;
37
- }
38
- /**
39
- * Typed, schema-scoped database facade for an addon. The host deliberately omits raw execution,
40
- * schema switching, dialect/connection access, and lifecycle methods.
41
- */
42
- type AddonDatabase<TDatabase = Record<string, Record<string, unknown>>> = Pick<Kysely<TDatabase>, "deleteFrom" | "fn" | "insertInto" | "selectFrom" | "transaction" | "updateTable">;
43
- /**
44
- * Context provided to an addon's server-side factory. Parameterize it with the addon's table
45
- * interface: `AddonServerContext<MyAddonDatabase>`.
46
- */
47
- interface AddonServerContext<TDatabase = Record<string, Record<string, unknown>>> {
48
- /**
49
- * Retrieve the encrypted settings for this addon for a specific site. Internal secrets are
50
- * automatically decrypted using the host's master key.
51
- */
52
- getConfig: (siteId: string) => Promise<Record<string, unknown>>;
53
- /** Fetch a specific user's public profile */
54
- getUser: (userId: string) => Promise<AddonUser | null>;
55
- /** Efficiently fetch multiple user profiles in a single query. Returns a Map keyed by user ID. */
56
- getUsers: (userIds: string[]) => Promise<Map<string, AddonUser>>;
57
- /**
58
- * Check whether the request user holds a given permission. Provides autocomplete for core Jchat
59
- * permissions while also accepting any custom addon permission string.
60
- *
61
- * @param user - The user object from the request context
62
- * @param permission - The permission to check, e.g., 'message.send' or 'addon.stories.view'
63
- */
64
- hasPermission: (user: AddonRequestUser | null | undefined, permission: AddonPermission) => boolean;
65
- /**
66
- * A Kysely database instance scoped entirely to your addon's tables. You cannot access core Jchat
67
- * tables (Users, Rooms, etc.) through this instance.
68
- */
69
- db: AddonDatabase<TDatabase>;
70
- }
71
- /**
72
- * The entry point for your addon's server bundle. You should default-export a function matching
73
- * this signature.
74
- *
75
- * @example
76
- * ;```ts
77
- * import { type AddonServerFactory } from '@jchat/addon-sdk'
78
- *
79
- * const factory: AddonServerFactory = ({ db, hasPermission }, app) => {
80
- * return app.get('/trending', async () => { ... })
81
- * }
82
- * export default factory
83
- * ```
84
- */
85
- type AddonServerFactory<TDatabase = Record<string, Record<string, unknown>>> = (ctx: AddonServerContext<TDatabase>, app: Elysia) => Elysia;
86
- /** Props passed to your addon's root React component by the Jchat host. */
87
- interface AddonProps {
88
- /** The mount point where this addon instance is rendered (e.g., 'chat-sidebar') */
89
- mountPoint: MountPoint;
90
- /** Information about the currently logged-in user (if any) */
91
- user?: {
92
- id: string;
93
- name: string;
94
- displayName?: string;
95
- avatar?: string;
96
- gender?: string;
97
- };
98
- /** The ID of the room the user is currently viewing (if applicable) */
99
- roomId?: string;
100
- /** The ID of the active Jchat site */
101
- siteId?: string;
102
- }
103
- interface AddonApi {
104
- get: <TResponse = unknown>(path: string) => Promise<TResponse>;
105
- post: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
106
- put: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
107
- delete: <TResponse = unknown>(path: string) => Promise<TResponse>;
108
- }
109
- type AddonEventListener = (data: unknown) => void;
110
- /** A font option provided by the JChat host. */
111
- interface AddonFont {
112
- /** Display label shown to users (e.g. 'Modern', 'Elegant') */
113
- label: string;
114
- /** Font identifier stored in the database (e.g. 'poppins', 'lato') */
115
- value: string;
116
- /** CSS font-family string ready for use in inline styles */
117
- family: string;
118
- /** Google Fonts name for loading the font via a <link> tag */
119
- googleName: string;
120
- /** Relative size multiplier — apply this to base font sizes to match the host's rendering */
121
- scale: number;
122
- }
123
- /**
124
- * The bridge between your addon and the Jchat host application. Use the `useAddonSDK()` hook to
125
- * access this interface in your components.
126
- */
127
- interface AddonSDK {
128
- /**
129
- * Make authenticated API calls to your addon's backend or core Jchat endpoints. All calls
130
- * automatically include the current user's sessionId and site validation headers.
131
- */
132
- api: AddonApi;
133
- /**
134
- * Display a temporary notification toast in the host UI.
135
- *
136
- * @param message - The text content to display
137
- * @param variant - Visual style: 'default' (blue), 'success' (green), or 'error' (red)
138
- */
139
- toast: (message: string, variant?: "default" | "success" | "error") => void;
140
- /**
141
- * Navigate the host application to a different internal route.
142
- *
143
- * @param path - The target location, e.g., '/settings' or '/addons/store'
144
- */
145
- navigate: (path: string) => void;
146
- /**
147
- * Interaction utilities for the active Slate message editor. Only available if your addon is
148
- * mounted near the chat input.
149
- */
150
- editor: {
151
- /**
152
- * Trigger a specialized editor command defined by the host.
153
- *
154
- * @example
155
- * sdk.editor.dispatchCommand("INSERT_YOUTUBE_COMMAND", "dQw4w9WgXcQ")
156
- */
157
- dispatchCommand: (command: string, payload?: unknown) => void;
158
- /**
159
- * Insert a low-level Slate node directly at the user's cursor position.
160
- *
161
- * @param node - A valid Slate Descendant or Fragment object
162
- */
163
- insertNode: (node: unknown) => void;
164
- /**
165
- * Upload and attach a file to the current message without inserting it inline. Users will see
166
- * the file in the attachment bar below the editor.
167
- */
168
- addAttachment: (attachment: {
169
- url: string;
170
- name: string;
171
- type: string;
172
- }) => void;
173
- /** Force the browser focus back to the editor input */
174
- focus: () => void;
175
- };
176
- /** Event Bus for reacting to real-time application state changes. */
177
- events: {
178
- /**
179
- * Register a persistent listener for a specific event.
180
- *
181
- * @param event - Event name, e.g., 'room.message', 'user.typing'
182
- */
183
- on: (event: string, callback: AddonEventListener) => void;
184
- /** Unregister a previously attached listener */
185
- off: (event: string, callback: AddonEventListener) => void;
186
- };
187
- /**
188
- * The list of fonts available in this JChat deployment, as configured by the site administrator.
189
- * Use this to build a font picker or apply a matching font family in your addon UI.
190
- */
191
- supportedFonts?: AddonFont[];
192
- }
193
- /**
194
- * Seed data for your addon's tables, provided inside the addon's isolated schema.
195
- *
196
- * These documents are inserted into the addon's dedicated schema on installation and can be used to
197
- * provide default values, references, or supporting data. The host guarantees that these documents
198
- * do not conflict with core schemas or other addons.
199
- */
200
- interface AddonSeedDocument {
201
- /** Addon-owned table name. Core prefixes the addon's isolated PostgreSQL schema. */
202
- table: string;
203
- /** Behavior when a row conflicts with an existing unique or primary key. */
204
- conflict?: "error" | "ignore" | "update";
205
- /** Required for update behavior; identifies the unique key used by ON CONFLICT. */
206
- conflictColumns?: string[];
207
- /** JSON-compatible rows inserted using parameterized values. */
208
- rows: Record<string, unknown>[];
209
- }
210
- /** Define a manifest with full validation-friendly typing while preserving literal values. */
211
- declare function defineAddonManifest<const TManifest extends AddonManifest>(manifest: TManifest): TManifest;
212
- /** Define a typed server factory without widening its addon database interface. */
213
- declare function defineAddonServer<TDatabase = Record<string, Record<string, unknown>>>(factory: AddonServerFactory<TDatabase>): AddonServerFactory<TDatabase>;
214
- /**
215
- * Access the host application's SDK. Provides authenticated API access, toast notifications, and
216
- * navigation.
217
- *
218
- * This function is a shell; the real implementation is provided by the Jchat host environment at
219
- * runtime.
220
- *
221
- * @example
222
- * ;```tsx
223
- * import { useAddonSDK } from "@jchat/addon-sdk"
224
- *
225
- * export default function MyAddon({ mountPoint }: AddonProps) {
226
- * const sdk = useAddonSDK()
227
- *
228
- * const handleClick = async () => {
229
- * const data = await sdk.api.get("/trending")
230
- * sdk.toast("Fetched trending posts!", "success")
231
- * }
232
- * }
233
- * ```
234
- */
235
- declare function useAddonSDK(): AddonSDK;
236
- /**
237
- * Access the host application's active icon pack. Returns the icon set matching the site's
238
- * configured icon pack preference.
239
- *
240
- * This function is a shell; the real implementation is provided by the Jchat host environment at
241
- * runtime.
242
- *
243
- * @example
244
- * ;```tsx
245
- * import { useIcons } from "@jchat/addon-sdk"
246
- *
247
- * export default function MyAddon() {
248
- * const { Icons } = useIcons()
249
- * return <Icons.Send className="size-5" />
250
- * }
251
- * ```
252
- */
253
- declare function useIcons(): {
254
- Icons: IconPack;
8
+ declare const fa6Pack: IconPack;
9
+ declare const heroPack: IconPack;
10
+ declare const phosphorPack: IconPack;
11
+ declare const IconPackName: {
12
+ readonly LUCIDE: "lucide";
255
13
  };
14
+ type IconPackName = (typeof IconPackName)[keyof typeof IconPackName];
15
+ declare const getIconPack: (_name?: string) => IconPack;
16
+ declare const isIconName: (value: string) => value is IconName;
17
+ declare function resolveIcon(packOrName?: IconPack | string, nameOrFallback?: string | IconName, fallback?: IconName): IconComponent;
18
+ declare const Icons: IconPack;
256
19
  //#endregion
257
- export { AddonApi, AddonDatabase, AddonEventListener, AddonFont, AddonJsonBody, type AddonManifest, type AddonPermission, type AddonPermissionDefinition, AddonProps, AddonRequestUser, AddonSDK, AddonSeedDocument, AddonServerContext, AddonServerFactory, AddonUser, type ConfigSchemaField, MOUNT_POINTS, type MountPoint, defineAddonManifest, defineAddonServer, useAddonSDK, useIcons };
20
+ export { getIconPack as a, lucidePack as c, fa6Pack as i, phosphorPack as l, Icons as n, heroPack as o, Youtube as r, isIconName as s, IconPackName as t, resolveIcon as u };
package/dist/index.mjs CHANGED
@@ -1,33 +1,98 @@
1
- //#region src/contracts.ts
2
- /** Valid locations where an addon can be rendered within the Jchat layout. */
3
- const MOUNT_POINTS = {
4
- /** Top of the main chat column */
5
- CHAT_HEADER: "chat-header",
6
- /** Below the main chat header, above messages */
7
- CHAT_TOOLBAR: "chat-toolbar",
8
- /** The persistent right-side sidebar */
9
- CHAT_SIDEBAR: "chat-sidebar",
10
- /** Top of the left sidebar (room list) */
11
- SIDEBAR_TOP: "sidebar-top",
12
- /** Bottom of the left sidebar */
13
- SIDEBAR_BOTTOM: "sidebar-bottom",
14
- /** Action list shown when clicking a user's avatar */
15
- USER_POPOVER_ACTIONS: "user-popover-actions",
16
- /** Menu shown when right-clicking a message */
17
- MESSAGE_CONTEXT_MENU: "message-context-menu",
18
- /** Floating overlay rendered on top of the message stream */
19
- MESSAGE_FEED_OVERLAY: "message-feed-overlay",
20
- /** Main admin dashboard area */
21
- ADMIN_PANEL: "admin-panel",
22
- /** User or site settings dashboard area */
23
- SETTINGS_PANEL: "settings-panel",
24
- /** The 'plus' (+) menu in the message composer */
25
- CHAT_PLUS_MENU: "chat-plus-menu",
26
- /** Custom tab in the emoji/gif selection panel */
27
- EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab",
28
- /** Full-screen or modal dialog specifically for TubeScan */
29
- TUBESCAN_DIALOG: "tubescan-dialog"
30
- };
1
+ import { MOUNT_POINTS } from "./contracts.mjs";
2
+ import { a as useTranslation, i as useLocale, n as LocaleContext, r as createTranslator, t as HOST_LANGUAGES } from "./i18n.mjs";
3
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ //#region package.json
5
+ var version = "1.0.0-alpha.21";
6
+ //#endregion
7
+ //#region src/query.ts
8
+ /**
9
+ * Perform a declarative, cached GET request using the host's shared React Query instance.
10
+ * Automatically injects authentication headers and prefixes addon paths.
11
+ *
12
+ * @example
13
+ * ;```tsx
14
+ * const { data, isLoading } = useAddonQuery<{ polls: Poll[] }>({
15
+ * path: "/polls",
16
+ * queryKey: [siteId]
17
+ * })
18
+ * ```
19
+ */
20
+ function useAddonQuery(options) {
21
+ const sdk = useAddonSDK();
22
+ const { path, queryKey = [], queryFn, ...queryOptions } = options;
23
+ const effectiveKey = path !== void 0 ? [path, ...queryKey] : queryKey;
24
+ const effectiveFn = queryFn ?? (() => {
25
+ if (!path) throw new Error("useAddonQuery requires either a `path` or a custom `queryFn`");
26
+ return sdk.api.get(path);
27
+ });
28
+ return useQuery({
29
+ ...queryOptions,
30
+ queryKey: effectiveKey,
31
+ queryFn: effectiveFn
32
+ });
33
+ }
34
+ /**
35
+ * Perform a paginated or infinite scrolling request using the host's shared React Query instance.
36
+ *
37
+ * @example
38
+ * ;```tsx
39
+ * const { data, fetchNextPage, hasNextPage } = useAddonInfiniteQuery<GifResponse, Error, number>({
40
+ * path: (page) => `/trending?offset=${page * 20}`,
41
+ * initialPageParam: 0,
42
+ * getNextPageParam: (lastPage, allPages) => (lastPage.hasMore ? allPages.length : undefined)
43
+ * })
44
+ * ```
45
+ */
46
+ function useAddonInfiniteQuery(options) {
47
+ const sdk = useAddonSDK();
48
+ const { path, queryKey = [], queryFn, ...infiniteOptions } = options;
49
+ const effectiveKey = [typeof path === "string" ? path : "infinite", ...queryKey];
50
+ const effectiveFn = queryFn ?? (async (context) => {
51
+ const pageParam = context.pageParam;
52
+ const resolvedPath = typeof path === "function" ? path(pageParam) : path;
53
+ if (!resolvedPath) throw new Error("useAddonInfiniteQuery requires either a `path` or a custom `queryFn`");
54
+ return sdk.api.get(resolvedPath);
55
+ });
56
+ return useInfiniteQuery({
57
+ ...infiniteOptions,
58
+ queryKey: effectiveKey,
59
+ queryFn: effectiveFn
60
+ });
61
+ }
62
+ /**
63
+ * Perform a declarative mutation using the host's shared React Query instance. Automatically
64
+ * executes the request and can invalidate specified query keys upon success.
65
+ *
66
+ * @example
67
+ * ;```tsx
68
+ * const { mutate, isPending } = useAddonMutation<Poll, CreatePollInput>({
69
+ * path: "/polls",
70
+ * method: "POST",
71
+ * invalidateQueries: [["/polls"]],
72
+ * onSuccess: () => sdk.toast("Poll created!", "success")
73
+ * })
74
+ * ```
75
+ */
76
+ function useAddonMutation(options) {
77
+ const sdk = useAddonSDK();
78
+ const queryClient = useQueryClient();
79
+ const { path, method = "POST", invalidateQueries = [], mutationFn, onSuccess, ...mutationOptions } = options;
80
+ const effectiveFn = mutationFn ?? (async (variables) => {
81
+ const resolvedPath = typeof path === "function" ? path(variables) : path ?? "";
82
+ if (!resolvedPath) throw new Error("useAddonMutation requires a valid 'path' or 'mutationFn'.");
83
+ if (method === "PUT") return sdk.api.put(resolvedPath, variables);
84
+ if (method === "DELETE") return sdk.api.delete(resolvedPath);
85
+ return sdk.api.post(resolvedPath, variables);
86
+ });
87
+ return useMutation({
88
+ ...mutationOptions,
89
+ mutationFn: effectiveFn,
90
+ onSuccess: async (...args) => {
91
+ if (invalidateQueries.length > 0) await Promise.all(invalidateQueries.map((key) => queryClient.invalidateQueries({ queryKey: key })));
92
+ if (onSuccess) await onSuccess(...args);
93
+ }
94
+ });
95
+ }
31
96
  //#endregion
32
97
  //#region src/index.ts
33
98
  /** Define a manifest with full validation-friendly typing while preserving literal values. */
@@ -60,27 +125,50 @@ function defineAddonServer(factory) {
60
125
  * ```
61
126
  */
62
127
  function useAddonSDK() {
128
+ const hostUseAddonSDK = typeof window !== "undefined" ? window.__JCHAT_EXTERNALS__?.JchatAddonSDK?.useAddonSDK : void 0;
129
+ if (hostUseAddonSDK && hostUseAddonSDK !== useAddonSDK) return hostUseAddonSDK();
63
130
  throw new Error("AddonSDK must be used within the Jchat host environment.");
64
131
  }
65
132
  /**
66
- * Access the host application's active icon pack. Returns the icon set matching the site's
67
- * configured icon pack preference.
68
- *
69
- * This function is a shell; the real implementation is provided by the Jchat host environment at
70
- * runtime.
133
+ * Get the version of the addon SDK. This is useful for checking compatibility with the host
134
+ * application.
71
135
  *
72
136
  * @example
73
137
  * ;```tsx
74
- * import { useIcons } from "@jchat/addon-sdk"
138
+ * import { getAddonSdkVersion } from "@jchat/addon-sdk"
75
139
  *
76
- * export default function MyAddon() {
77
- * const { Icons } = useIcons()
78
- * return <Icons.Send className="size-5" />
79
- * }
140
+ * console.log(getAddonSdkVersion())
80
141
  * ```
142
+ *
143
+ * @returns
144
+ */
145
+ function getAddonSdkVersion() {
146
+ return version;
147
+ }
148
+ /**
149
+ * Verifies purchase entitlement and identity integrity for commercial addons. Throws an error if
150
+ * the addon fails entitlement verification or if its manifest ID has been tampered with to
151
+ * impersonate another addon.
152
+ *
153
+ * @example
154
+ * ;```ts
155
+ * export default defineAddonServer((context, app) => {
156
+ * verifyAddonEntitlement("tubescanpro", context)
157
+ * return app.get("/trending", async () => { ... })
158
+ * })
159
+ * ```
160
+ *
161
+ * @param addonId - The canonical addon ID defined by the publisher (e.g. 'tubescanpro')
162
+ * @param context - The AddonServerContext passed to the addon's server factory
163
+ * @returns True if valid
164
+ * @throws Error if entitlement verification fails
81
165
  */
82
- function useIcons() {
83
- throw new Error("useIcons must be used within the Jchat host environment.");
166
+ function verifyAddonEntitlement(addonId, context) {
167
+ if (context?.verifyEntitlement) {
168
+ if (!context.verifyEntitlement(addonId)) throw new Error(`addon_entitlement_verification_failed:${addonId}`);
169
+ return true;
170
+ }
171
+ return true;
84
172
  }
85
173
  //#endregion
86
- export { MOUNT_POINTS, defineAddonManifest, defineAddonServer, useAddonSDK, useIcons };
174
+ export { HOST_LANGUAGES, LocaleContext, MOUNT_POINTS, createTranslator, defineAddonManifest, defineAddonServer, getAddonSdkVersion, useAddonInfiniteQuery, useAddonMutation, useAddonQuery, useAddonSDK, useLocale, useQueryClient, useTranslation, verifyAddonEntitlement };