@jchat/addon-sdk 1.0.0-alpha.20 → 1.0.0-alpha.22

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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # @jchat/addon-sdk
2
2
 
3
+ ## 1.0.0-alpha.22
4
+
5
+ ### Minor Changes
6
+
7
+ - Introduce composer preview slot, active addon coordination, and lifecycle optimizations
8
+
9
+ - Add CHAT_COMPOSER_PREVIEW mount point for live composer rich card previews
10
+ - Optimize message lifecycle pipeline with active addon caching and AbortSignal cancellation
11
+ - Allow passing verifyEntitlement function directly to verifyAddonEntitlement
12
+ - Provide activeAddonIds in AddonProps to enable addon self-coordination
13
+ - Update default service and documentation domains to jchatplatform.com
14
+
15
+ ### Patch Changes
16
+
17
+ - Bumped `@jchat/*` dependencies:
18
+ - [`ui@1.0.0-alpha.22`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.22)
19
+
20
+ ## 1.0.0-alpha.21
21
+
22
+ ### Minor Changes
23
+
24
+ - Add message lifecycle interception hooks and chat message card mount point support
25
+
26
+ - Add TypeScript typings for available editor commands in Addon SDK
27
+
28
+ - Add verifyAddonEntitlement runtime helper and AddonServerContext method
29
+
30
+ ### Patch Changes
31
+
32
+ - Remove redundant boilerplate test files
33
+
34
+ - Enhance add-on CSS compilation to ensure consistent styling across dialogs and portals
35
+
36
+ - Bumped `@jchat/*` dependencies:
37
+ - [`icon@1.0.0-alpha.21`](https://github.com/mjjabarullah/jchat/releases/tag/icon@v1.0.0-alpha.21)
38
+ - [`ui@1.0.0-alpha.21`](https://github.com/mjjabarullah/jchat/releases/tag/ui@v1.0.0-alpha.21)
39
+
3
40
  ## 1.0.0-alpha.20
4
41
 
5
42
  ### Patch Changes
@@ -57,7 +94,7 @@ No changes in this release.
57
94
  - c5fa55f: Support direct icon imports via `@jchat/addon-sdk/icon` and align icon packs with canonical Lucide icon definitions. Addons can now import `Icons` directly without requiring hook invocation (`useIcons`), while maintaining backward compatibility for existing addons.
58
95
  - 27efa80: - Update third-party developer guide in README with complete quick start, architecture specifications, hook references, and mount points
59
96
  - Include `LICENSE` file in published npm package distribution
60
- - Update license badge and terms references to link to official public web address (`https://jchatlive.com/terms`)
97
+ - Update license badge and terms references to link to official public web address (`https://jchatplatform.com/terms`)
61
98
 
62
99
  ## 1.0.0-alpha.11
63
100
 
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @jchat/addon-sdk
2
2
 
3
- Official TypeScript SDK for developing extensions, UI widgets, and server plugins for the [JChat](https://jchatlive.com) communication platform.
3
+ Official TypeScript SDK for developing extensions, UI widgets, and server plugins for the [JChat](https://jchatplatform.com) communication platform.
4
4
 
5
5
  Provides typed interfaces, manifest contracts, Vite bundler plugins, and CLI development tooling for building sandboxed JChat addons.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@jchat/addon-sdk.svg)](https://www.npmjs.com/package/@jchat/addon-sdk)
8
- [![License](https://img.shields.io/badge/license-Proprietary-blue.svg)](https://jchatlive.com/terms)
8
+ [![License](https://img.shields.io/badge/license-Proprietary-blue.svg)](https://jchatplatform.com/terms)
9
9
 
10
10
  ---
11
11
 
@@ -401,10 +401,10 @@ bun run build
401
401
  1. Build your addon package: `bun run build`.
402
402
  2. Ensure all 20 translation files pass validation: `bun test`.
403
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.
404
+ 4. Sign in to the **[JChat Store Developer Portal](https://jchatplatform.com/addons)** and submit your addon package for certified distribution.
405
405
 
406
406
  ---
407
407
 
408
408
  ## License
409
409
 
410
- Proprietary — All rights reserved. See the [Commercial EULA & Licensing Terms](https://jchatlive.com/terms) for full details.
410
+ Proprietary — All rights reserved. See the [Commercial EULA & Licensing Terms](https://jchatplatform.com/terms) for full details.
@@ -36,15 +36,36 @@ export declare const MOUNT_POINTS: {
36
36
  readonly EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab";
37
37
  /** Custom tab in the emoji/sticker selection panel */
38
38
  readonly EMOJI_PICKER_STICKER_TAB: "emoji-picker-sticker-tab";
39
- /** Full-screen or modal dialog specifically for TubeScan */
40
- readonly TUBESCAN_DIALOG: "tubescan-dialog";
41
39
  /** Dedicated settings panel rendered in the Owner Dashboard */
42
40
  readonly OWNER_SETTINGS: "owner-settings";
43
41
  /** Dedicated settings panel rendered in the Site Admin Dashboard */
44
42
  readonly ADDON_SETTINGS: "addon-settings";
43
+ /** Custom interactive card rendered inside an enriched chat message */
44
+ readonly CHAT_MESSAGE_CARD: "chat-message-card";
45
+ /** Custom badge, audio equalizer, or particle aura rendered on a chat bubble */
46
+ readonly CHAT_MESSAGE_FLAIR: "chat-message-flair";
47
+ /** Custom interactive preview card rendered in the message composer */
48
+ readonly CHAT_COMPOSER_PREVIEW: "chat-composer-preview";
45
49
  };
46
50
  /** Union type of all valid addon mount points. */
47
51
  export type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
52
+ /** Parameter specification for an addon slash command. */
53
+ export interface AddonCommandParameter {
54
+ name: string;
55
+ description?: string;
56
+ required?: boolean;
57
+ }
58
+ /** Declaration of an addon slash command available in the chat composer. */
59
+ export interface AddonCommandDefinition {
60
+ /** The command keyword without leading slash (e.g. 'yt', 'roll', 'poll') */
61
+ name: string;
62
+ /** Short summary of what the command does, displayed in autocomplete */
63
+ description: string;
64
+ /** Usage syntax displayed as a hint (e.g. '<query | url>') */
65
+ syntax?: string;
66
+ /** Parameters accepted by this command */
67
+ parameters?: AddonCommandParameter[];
68
+ }
48
69
  /**
49
70
  * The `manifest.json` schema required for every Jchat addon. This file describes how the addon is
50
71
  * identified and mounted.
@@ -117,5 +138,7 @@ export interface AddonManifest {
117
138
  permissions?: string[];
118
139
  /** Custom permissions that should be registered with the host application on installation */
119
140
  permissionDefinitions?: AddonPermissionDefinition[];
141
+ /** Interactive slash commands exposed in the chat composer */
142
+ commands?: AddonCommandDefinition[];
120
143
  }
121
144
  //#endregion
@@ -27,12 +27,16 @@ const MOUNT_POINTS = {
27
27
  EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab",
28
28
  /** Custom tab in the emoji/sticker selection panel */
29
29
  EMOJI_PICKER_STICKER_TAB: "emoji-picker-sticker-tab",
30
- /** Full-screen or modal dialog specifically for TubeScan */
31
- TUBESCAN_DIALOG: "tubescan-dialog",
32
30
  /** Dedicated settings panel rendered in the Owner Dashboard */
33
31
  OWNER_SETTINGS: "owner-settings",
34
32
  /** Dedicated settings panel rendered in the Site Admin Dashboard */
35
- ADDON_SETTINGS: "addon-settings"
33
+ ADDON_SETTINGS: "addon-settings",
34
+ /** Custom interactive card rendered inside an enriched chat message */
35
+ CHAT_MESSAGE_CARD: "chat-message-card",
36
+ /** Custom badge, audio equalizer, or particle aura rendered on a chat bubble */
37
+ CHAT_MESSAGE_FLAIR: "chat-message-flair",
38
+ /** Custom interactive preview card rendered in the message composer */
39
+ CHAT_COMPOSER_PREVIEW: "chat-composer-preview"
36
40
  };
37
41
  //#endregion
38
42
  export { MOUNT_POINTS };
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { MOUNT_POINTS } from "./contracts.mjs";
2
2
  import { a as useTranslation, i as useLocale, n as LocaleContext, r as createTranslator, t as HOST_LANGUAGES } from "./i18n.mjs";
3
3
  import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  //#region package.json
5
- var version = "1.0.0-alpha.19";
5
+ var version = "1.0.0-alpha.21";
6
6
  //#endregion
7
7
  //#region src/query.ts
8
8
  /**
@@ -145,5 +145,32 @@ function useAddonSDK() {
145
145
  function getAddonSdkVersion() {
146
146
  return version;
147
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 ({ verifyEntitlement, db, t }: AddonServerContext, app: AddonApp) => {
156
+ * verifyAddonEntitlement("tubescanpro", verifyEntitlement)
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 target - Either the `verifyEntitlement` function from context or the `AddonServerContext`
163
+ * object itself
164
+ * @returns True if valid
165
+ * @throws Error if entitlement verification fails
166
+ */
167
+ function verifyAddonEntitlement(addonId, target) {
168
+ const verifyFn = typeof target === "function" ? target : target?.verifyEntitlement;
169
+ if (verifyFn) {
170
+ if (!verifyFn(addonId)) throw new Error(`addon_entitlement_verification_failed:${addonId}`);
171
+ return true;
172
+ }
173
+ return true;
174
+ }
148
175
  //#endregion
149
- export { HOST_LANGUAGES, LocaleContext, MOUNT_POINTS, createTranslator, defineAddonManifest, defineAddonServer, getAddonSdkVersion, useAddonInfiniteQuery, useAddonMutation, useAddonQuery, useAddonSDK, useLocale, useQueryClient, useTranslation };
176
+ export { HOST_LANGUAGES, LocaleContext, MOUNT_POINTS, createTranslator, defineAddonManifest, defineAddonServer, getAddonSdkVersion, useAddonInfiniteQuery, useAddonMutation, useAddonQuery, useAddonSDK, useLocale, useQueryClient, useTranslation, verifyAddonEntitlement };
package/dist/index2.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { i as IconPack, n as IconComponent, r as IconName } from "./types.mjs";
2
- import { AddonManifest, AddonPermissionDefinition, MOUNT_POINTS, MountPoint } from "./contracts.mjs";
2
+ import { AddonCommandDefinition, AddonCommandParameter, AddonManifest, AddonPermissionDefinition, MOUNT_POINTS, MountPoint } from "./contracts.mjs";
3
3
  import { InfiniteData, MutationFunction, QueryFunction, QueryFunctionContext, QueryKey, UndefinedInitialDataInfiniteOptions, UndefinedInitialDataOptions, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult, UseQueryResult, useQueryClient } from "@tanstack/react-query";
4
4
  import { Elysia, t } from "elysia";
5
5
  import { ColumnType, Generated, Insertable, JSONColumnType, Kysely, Selectable, Updateable } from "kysely";
@@ -209,6 +209,70 @@ export interface AddonServerEmitTarget {
209
209
  /** Target a specific user */
210
210
  userId?: string;
211
211
  }
212
+ export interface AddonMessageMutableData {
213
+ /** Message content (can be Slate JSON structure or string) */
214
+ content: unknown;
215
+ /** Optional message attachments */
216
+ attachments?: unknown[] | null;
217
+ /**
218
+ * Namespaced metadata bucket for this addon. Keyed as `metadata["addon:<addonId>"]`. Storing data
219
+ * here automatically attaches it to the message broadcast and database record.
220
+ */
221
+ metadata: Record<string, unknown>;
222
+ }
223
+ export interface AddonMessageContext {
224
+ /** The site ID where the message is being sent */
225
+ siteId: string;
226
+ /** The chat room ID */
227
+ roomId: string;
228
+ /** The authenticated user sending the message (read-only) */
229
+ user: AddonRequestUser;
230
+ /** Mutable message payload */
231
+ message: AddonMessageMutableData;
232
+ /** Convenience method to set metadata under this addon's namespace */
233
+ setMetadata: (key: string, value: unknown) => void;
234
+ /**
235
+ * Abort the message sending pipeline with a user-facing error code and reason. Only active during
236
+ * `beforeValidate` and `transformContent`.
237
+ */
238
+ abort: (error: {
239
+ code: string;
240
+ reason?: string;
241
+ }) => never;
242
+ /**
243
+ * AbortSignal triggered if this interceptor times out or the message pipeline is cancelled. Can
244
+ * be passed to `fetch(url, { signal: context.signal })` or async operations.
245
+ */
246
+ signal?: AbortSignal;
247
+ }
248
+ export interface AddonMessageAfterSaveContext {
249
+ siteId: string;
250
+ roomId: string;
251
+ user: AddonRequestUser;
252
+ message: {
253
+ id: string;
254
+ content: unknown;
255
+ attachments?: unknown[] | null;
256
+ metadata?: Record<string, unknown> | null;
257
+ type: string;
258
+ createdAt: Date | string;
259
+ };
260
+ /** AbortSignal triggered if the observer times out or is cancelled. */
261
+ signal?: AbortSignal;
262
+ }
263
+ export interface AddonPipelineHooks {
264
+ /** Optional priority ordering. Lower numbers execute first (e.g. 10 runs before 100). Default is 100. */
265
+ priority?: number;
266
+ /**
267
+ * Run before validation to inspect or guard actions (rate-limits, permission gates). Throw or
268
+ * call `context.abort()` to reject the message.
269
+ */
270
+ beforeValidate?: (context: AddonMessageContext) => Promise<void> | void;
271
+ /** Run before persistence to mutate text, parse slash commands, or enrich metadata. */
272
+ transformContent?: (context: AddonMessageContext) => Promise<void> | void;
273
+ /** Run asynchronously after database save (fire-and-forget observers). */
274
+ afterSave?: (context: AddonMessageAfterSaveContext) => Promise<void> | void;
275
+ }
212
276
  /**
213
277
  * Context provided to an addon's server-side factory. Parameterize it with the addon's table
214
278
  * interface: `AddonServerContext<MyAddonDatabase>`.
@@ -249,6 +313,21 @@ export interface AddonServerContext<TDatabase = Record<string, Record<string, un
249
313
  * `sdk.events.on('addon:<addonId>:<event>', callback)` or `sdk.events.on(event, callback)`.
250
314
  */
251
315
  emit: (event: string, payload: unknown, target?: AddonServerEmitTarget) => Promise<void>;
316
+ /**
317
+ * Register lifecycle interception hooks for chat messages. Hooks run in the core chat execution
318
+ * path with strict 50ms timeout guards and fail-open fault tolerance.
319
+ */
320
+ pipeline?: {
321
+ register: (hooks: AddonPipelineHooks) => void;
322
+ };
323
+ /**
324
+ * Cryptographically verify that this addon instance holds a valid host license entitlement.
325
+ * Commercial paid addons should invoke this or call `verifyAddonEntitlement(addonId, context)`.
326
+ *
327
+ * @param expectedAddonId - The canonical addon ID defined by the publisher
328
+ * @returns True if valid, or false if entitlement is missing or invalid
329
+ */
330
+ verifyEntitlement?: (expectedAddonId?: string) => boolean;
252
331
  }
253
332
  /**
254
333
  * Elysia app instance provided to addon server endpoints with Jchat macro metadata (e.g.
@@ -309,6 +388,12 @@ export interface AddonProps {
309
388
  locale?: string;
310
389
  /** Text direction for the current locale */
311
390
  dir?: "ltr" | "rtl";
391
+ /** The message object when rendered inside 'chat-message-card' or 'chat-message-flair' */
392
+ message?: unknown;
393
+ /** The addon's specific payload extracted from message.metadata */
394
+ addonData?: unknown;
395
+ /** List of active addon IDs enabled on the current site */
396
+ activeAddonIds?: string[];
312
397
  }
313
398
  export interface AddonApi {
314
399
  get: <TResponse = unknown>(path: string) => Promise<TResponse>;
@@ -337,6 +422,59 @@ export interface AddonFont {
337
422
  /** Relative size multiplier — apply this to base font sizes to match the host's rendering */
338
423
  scale: number;
339
424
  }
425
+ export interface AddonEditorInsertImagePayload {
426
+ src: string;
427
+ altText: string;
428
+ emoji?: {
429
+ col: number;
430
+ row: number;
431
+ size: number;
432
+ };
433
+ className?: string;
434
+ }
435
+ export interface AddonEditorInsertEmojiPayload {
436
+ col: number;
437
+ row: number;
438
+ unicode: string;
439
+ shortcode?: string;
440
+ }
441
+ export interface AddonEditorCommandMap {
442
+ INSERT_YOUTUBE_COMMAND: string;
443
+ INSERT_TEXT: string;
444
+ INSERT_TEXT_COMMAND: string;
445
+ INSERT_MENTION_COMMAND: string;
446
+ INSERT_IMAGE_COMMAND: AddonEditorInsertImagePayload;
447
+ INSERT_EMOJI_COMMAND: AddonEditorInsertEmojiPayload;
448
+ CLEAR_EDITOR: void;
449
+ CLEAR_EDITOR_COMMAND: void;
450
+ }
451
+ export type AddonEditorCommand = keyof AddonEditorCommandMap;
452
+ export interface AddonEditorApi {
453
+ /**
454
+ * Trigger a specialized editor command defined by the host.
455
+ *
456
+ * @example
457
+ * sdk.editor.dispatchCommand("INSERT_YOUTUBE_COMMAND", "dQw4w9WgXcQ")
458
+ */
459
+ dispatchCommand: <C extends AddonEditorCommand>(command: C, ...args: AddonEditorCommandMap[C] extends void ? [payload?: undefined] : [payload: AddonEditorCommandMap[C]]) => void;
460
+ /**
461
+ * Insert a low-level Slate node directly at the user's cursor position.
462
+ *
463
+ * @param node - A valid Slate Descendant or Fragment object
464
+ */
465
+ insertNode: (node: unknown) => void;
466
+ /**
467
+ * Upload and attach a file to the current message without inserting it inline. Users will see the
468
+ * file in the attachment bar below the editor.
469
+ */
470
+ addAttachment: (attachment: {
471
+ url: string;
472
+ name: string;
473
+ type: string;
474
+ }) => void;
475
+ /** Force the browser focus back to the editor input */
476
+ focus: () => void;
477
+ }
340
478
  /**
341
479
  * The bridge between your addon and the Jchat host application. Use the `useAddonSDK()` hook to
342
480
  * access this interface in your components.
@@ -370,32 +508,7 @@ export interface AddonSDK {
370
508
  * Interaction utilities for the active Slate message editor. Only available if your addon is
371
509
  * mounted near the chat input.
372
510
  */
373
- editor: {
374
- /**
375
- * Trigger a specialized editor command defined by the host.
376
- *
377
- * @example
378
- * sdk.editor.dispatchCommand("INSERT_YOUTUBE_COMMAND", "dQw4w9WgXcQ")
379
- */
380
- dispatchCommand: (command: string, payload?: unknown) => void;
381
- /**
382
- * Insert a low-level Slate node directly at the user's cursor position.
383
- *
384
- * @param node - A valid Slate Descendant or Fragment object
385
- */
386
- insertNode: (node: unknown) => void;
387
- /**
388
- * Upload and attach a file to the current message without inserting it inline. Users will see
389
- * the file in the attachment bar below the editor.
390
- */
391
- addAttachment: (attachment: {
392
- url: string;
393
- name: string;
394
- type: string;
395
- }) => void;
396
- /** Force the browser focus back to the editor input */
397
- focus: () => void;
398
- };
511
+ editor: AddonEditorApi;
399
512
  /** Event Bus for reacting to real-time application state changes. */
400
513
  events: {
401
514
  /**
@@ -474,5 +587,27 @@ export declare function useAddonSDK(): AddonSDK;
474
587
  * @returns
475
588
  */
476
589
  export declare function getAddonSdkVersion(): string;
590
+ /**
591
+ * Verifies purchase entitlement and identity integrity for commercial addons. Throws an error if
592
+ * the addon fails entitlement verification or if its manifest ID has been tampered with to
593
+ * impersonate another addon.
594
+ *
595
+ * @example
596
+ * ;```ts
597
+ * export default ({ verifyEntitlement, db, t }: AddonServerContext, app: AddonApp) => {
598
+ * verifyAddonEntitlement("tubescanpro", verifyEntitlement)
599
+ * return app.get("/trending", async () => { ... })
600
+ * }
601
+ * ```
602
+ *
603
+ * @param addonId - The canonical addon ID defined by the publisher (e.g. 'tubescanpro')
604
+ * @param target - Either the `verifyEntitlement` function from context or the `AddonServerContext`
605
+ * object itself
606
+ * @returns True if valid
607
+ * @throws Error if entitlement verification fails
608
+ */
609
+ export declare function verifyAddonEntitlement(addonId: string, target?: ((expectedAddonId?: string) => boolean) | {
610
+ verifyEntitlement?: (expectedAddonId?: string) => boolean;
611
+ }): boolean;
477
612
  //#endregion
478
- export { type AddonManifest, type AddonPermissionDefinition, type ColumnType, type Generated, type IconComponent, type IconName, type IconPack, type InfiniteData, type Insertable, type JSONColumnType, MOUNT_POINTS, type MountPoint, type QueryKey, type Selectable, type Updateable, useQueryClient };
613
+ export { type AddonCommandDefinition, type AddonCommandParameter, type AddonManifest, type AddonPermissionDefinition, type ColumnType, type Generated, type IconComponent, type IconName, type IconPack, type InfiniteData, type Insertable, type JSONColumnType, MOUNT_POINTS, type MountPoint, type QueryKey, type Selectable, type Updateable, useQueryClient };
package/dist/vite.mjs CHANGED
@@ -82,7 +82,7 @@ function jchatAddon(options) {
82
82
  if (!fs.existsSync(buildDir)) return;
83
83
  const bundleFile = fs.readdirSync(buildDir).find((f) => f.includes("ui") && f.endsWith(".js"));
84
84
  if (!bundleFile) return;
85
- if (options.css !== false) embedCssIntoBundle(buildDir, manifestData.id);
85
+ if (options.css !== false) embedCssIntoBundle(buildDir);
86
86
  let manifest = {
87
87
  ...manifestData,
88
88
  version: pkg.version,
@@ -243,7 +243,7 @@ function requireAddonSigningKey(signingPrivateKey, developmentBuild) {
243
243
  if (developmentBuild) return void 0;
244
244
  throw new Error("Database-bearing addon builds require JCHAT_ADDON_SIGNING_PRIVATE_KEY");
245
245
  }
246
- function embedCssIntoBundle(buildDir, addonId) {
246
+ function embedCssIntoBundle(buildDir) {
247
247
  const files = fs.readdirSync(buildDir);
248
248
  const cssFile = files.find((f) => f.endsWith(".css"));
249
249
  const jsFile = files.find((f) => f.includes("ui") && f.endsWith(".js"));
@@ -274,7 +274,7 @@ function embedCssIntoBundle(buildDir, addonId) {
274
274
  css = css.replace(/\.user-border\s*\{[^}]+\}/g, "");
275
275
  css = css.replace(/\.scrollbar-hide[^{]*\{[^}]+\}/g, "");
276
276
  css = css.replace(/@layer\s+components(?:\s*\{[^}]*\}|;)/g, "");
277
- css = minifyCss(`@scope (#addon-${addonId}) { ${css} }`);
277
+ css = minifyCss(css);
278
278
  }
279
279
  if (css.trim().length > 0) {
280
280
  const injection = `
@@ -294,12 +294,14 @@ function embedCssIntoBundle(buildDir, addonId) {
294
294
  } catch {}
295
295
  }
296
296
  function stripLayer(css, layerName) {
297
- const marker = `@layer ${layerName} {`;
298
- let idx = css.indexOf(marker);
299
- while (idx !== -1) {
300
- let braceCount = 0;
301
- let endIdx = idx + marker.length - 1;
302
- for (let i = idx + marker.length - 1; i < css.length; i++) if (css[i] === "{") braceCount++;
297
+ const regex = new RegExp(`@layer\\s+${layerName}\\s*\\{`);
298
+ let match = regex.exec(css);
299
+ while (match) {
300
+ const startIdx = match.index;
301
+ const openBraceIdx = startIdx + match[0].length - 1;
302
+ let braceCount = 1;
303
+ let endIdx = -1;
304
+ for (let i = openBraceIdx + 1; i < css.length; i++) if (css[i] === "{") braceCount++;
303
305
  else if (css[i] === "}") {
304
306
  braceCount--;
305
307
  if (braceCount === 0) {
@@ -307,8 +309,9 @@ function stripLayer(css, layerName) {
307
309
  break;
308
310
  }
309
311
  }
310
- css = css.slice(0, idx) + css.slice(endIdx + 1);
311
- idx = css.indexOf(marker);
312
+ if (endIdx === -1) break;
313
+ css = css.slice(0, startIdx) + css.slice(endIdx + 1);
314
+ match = regex.exec(css);
312
315
  }
313
316
  return css;
314
317
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jchat/addon-sdk",
3
- "version": "1.0.0-alpha.20",
3
+ "version": "1.0.0-alpha.22",
4
4
  "private": false,
5
5
  "description": "JChat Addon SDK for building JChat addons.",
6
6
  "keywords": [