@get-bb/plugin-sdk 0.4.6 → 0.4.9

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.
@@ -0,0 +1,555 @@
1
+ // src/internal/composer-customization-validation.ts
2
+ var PLUGIN_SLOT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
3
+ var PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
4
+ function requireSlotId(kind, value) {
5
+ if (typeof value !== "string" || !PLUGIN_SLOT_ID_PATTERN.test(value)) {
6
+ throw new Error(
7
+ `${kind}: "id" must match ${String(PLUGIN_SLOT_ID_PATTERN)}, got ${JSON.stringify(value)}`
8
+ );
9
+ }
10
+ return value;
11
+ }
12
+ function requireProviderId(kind, value) {
13
+ if (typeof value !== "string" || !PLUGIN_SLOT_ID_PATTERN.test(value)) {
14
+ throw new Error(
15
+ `${kind}: "providerId" must match ${String(PLUGIN_SLOT_ID_PATTERN)}, got ${JSON.stringify(value)}`
16
+ );
17
+ }
18
+ return value;
19
+ }
20
+ function requireMessageDirectiveId(kind, value) {
21
+ if (typeof value !== "string" || !PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN.test(value)) {
22
+ throw new Error(
23
+ `${kind}: "id" must match ${String(PLUGIN_MESSAGE_DIRECTIVE_ID_PATTERN)}, got ${JSON.stringify(value)}`
24
+ );
25
+ }
26
+ return value;
27
+ }
28
+ function requireNonEmptyString(kind, field, value) {
29
+ if (typeof value !== "string" || value.length === 0) {
30
+ throw new Error(`${kind}: "${field}" must be a non-empty string`);
31
+ }
32
+ return value;
33
+ }
34
+ function requireOptionalString(kind, field, value) {
35
+ if (value !== void 0 && typeof value !== "string") {
36
+ throw new Error(`${kind}: "${field}" must be a string when set`);
37
+ }
38
+ return value;
39
+ }
40
+ function requireComponent(kind, value) {
41
+ if (typeof value !== "function") {
42
+ throw new Error(`${kind}: "component" must be a React component function`);
43
+ }
44
+ return value;
45
+ }
46
+ function requireFunction(kind, field, value) {
47
+ if (typeof value !== "function") {
48
+ throw new Error(`${kind}: "${field}" must be a function`);
49
+ }
50
+ return value;
51
+ }
52
+ function requireUniqueId(kind, seen, id) {
53
+ if (seen.has(id)) {
54
+ throw new Error(`${kind}: duplicate id "${id}"`);
55
+ }
56
+ seen.add(id);
57
+ }
58
+ function parseContributionArray(kind, value, onRejected, parse) {
59
+ if (value === void 0) return void 0;
60
+ if (!Array.isArray(value)) {
61
+ onRejected(`${kind}: must be an array when set`);
62
+ return void 0;
63
+ }
64
+ const seenIds = /* @__PURE__ */ new Set();
65
+ const parsed = [];
66
+ for (const [index, entry] of value.entries()) {
67
+ const entryKind = `${kind}[${index}]`;
68
+ try {
69
+ const parsedEntry = parse(entryKind, entry);
70
+ requireUniqueId(entryKind, seenIds, parsedEntry.id);
71
+ parsed.push(parsedEntry);
72
+ } catch (error) {
73
+ onRejected(error instanceof Error ? error.message : String(error));
74
+ }
75
+ }
76
+ return parsed;
77
+ }
78
+ function parseRegions(kind, registration, onRejected) {
79
+ const actions = parseContributionArray(`${kind}.actions`, registration.actions, onRejected, (entryKind, value) => {
80
+ const entry = value;
81
+ return {
82
+ id: requireSlotId(entryKind, entry?.id),
83
+ component: requireComponent(entryKind, entry?.component)
84
+ };
85
+ });
86
+ const banners = parseContributionArray(`${kind}.banners`, registration.banners, onRejected, (entryKind, value) => {
87
+ const entry = value;
88
+ const id = requireSlotId(entryKind, entry?.id);
89
+ const chrome = entry?.chrome;
90
+ if (chrome !== void 0 && chrome !== "card" && chrome !== "bare") {
91
+ throw new Error(
92
+ `${entryKind}: "chrome" must be "card" or "bare" when set`
93
+ );
94
+ }
95
+ return {
96
+ id,
97
+ ...chrome !== void 0 ? { chrome } : {},
98
+ component: requireComponent(entryKind, entry?.component)
99
+ };
100
+ });
101
+ const plusMenu = parseContributionArray(
102
+ `${kind}.plusMenu`,
103
+ registration.plusMenu,
104
+ onRejected,
105
+ (entryKind, value) => {
106
+ const entry = value;
107
+ const id = requireSlotId(entryKind, entry?.id);
108
+ const icon = requireOptionalString(entryKind, "icon", entry?.icon);
109
+ const description = requireOptionalString(
110
+ entryKind,
111
+ "description",
112
+ entry?.description
113
+ );
114
+ const disabled = entry?.disabled;
115
+ if (disabled !== void 0 && typeof disabled !== "boolean" && typeof disabled !== "function") {
116
+ throw new Error(
117
+ `${entryKind}: "disabled" must be a boolean or function when set`
118
+ );
119
+ }
120
+ return {
121
+ id,
122
+ label: requireNonEmptyString(entryKind, "label", entry?.label),
123
+ ...icon !== void 0 ? { icon } : {},
124
+ ...description !== void 0 ? { description } : {},
125
+ ...disabled !== void 0 ? {
126
+ disabled
127
+ } : {},
128
+ run: requireFunction(entryKind, "run", entry?.run)
129
+ };
130
+ }
131
+ );
132
+ let richText;
133
+ if (registration.richText !== void 0) {
134
+ const raw = registration.richText;
135
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
136
+ onRejected(`${kind}.richText: must be an object when set`);
137
+ } else {
138
+ const effects = parseContributionArray(
139
+ `${kind}.richText.effects`,
140
+ raw.effects,
141
+ onRejected,
142
+ (entryKind, value) => {
143
+ const entry = value;
144
+ return {
145
+ id: requireSlotId(entryKind, entry?.id),
146
+ match: requireFunction(entryKind, "match", entry?.match),
147
+ className: requireNonEmptyString(
148
+ entryKind,
149
+ "className",
150
+ entry?.className
151
+ )
152
+ };
153
+ }
154
+ );
155
+ const onDraftChange = raw.onDraftChange;
156
+ if (onDraftChange !== void 0 && typeof onDraftChange !== "function") {
157
+ onRejected(
158
+ `${kind}.richText: "onDraftChange" must be a function when set`
159
+ );
160
+ }
161
+ richText = {
162
+ ...effects !== void 0 ? { effects } : {},
163
+ ...typeof onDraftChange === "function" ? {
164
+ onDraftChange
165
+ } : {}
166
+ };
167
+ }
168
+ }
169
+ return {
170
+ ...actions !== void 0 ? { actions } : {},
171
+ ...banners !== void 0 ? { banners } : {},
172
+ ...plusMenu !== void 0 ? { plusMenu } : {},
173
+ ...richText !== void 0 ? { richText } : {}
174
+ };
175
+ }
176
+ function collectComposerCustomization(registration, seenIds, onRejected) {
177
+ const kind = "composer.customize";
178
+ try {
179
+ const raw = registration;
180
+ const id = requireSlotId(kind, raw?.id);
181
+ const scopes = raw?.scopes;
182
+ if (scopes !== void 0) {
183
+ if (!Array.isArray(scopes)) {
184
+ throw new Error(`${kind}: "scopes" must be an array when set`);
185
+ }
186
+ for (const scope of scopes) {
187
+ if (scope !== "thread" && scope !== "queued-message" && scope !== "side-chat" && scope !== "new-thread") {
188
+ throw new Error(
189
+ `${kind}: invalid scope kind ${JSON.stringify(scope)}`
190
+ );
191
+ }
192
+ }
193
+ }
194
+ requireUniqueId(kind, seenIds, id);
195
+ return {
196
+ id,
197
+ ...scopes !== void 0 ? { scopes: [...scopes] } : {},
198
+ ...parseRegions(`${kind}(${id})`, raw ?? {}, onRejected)
199
+ };
200
+ } catch (error) {
201
+ onRejected(error instanceof Error ? error.message : String(error));
202
+ return null;
203
+ }
204
+ }
205
+
206
+ // src/internal/plugin-app-collector.ts
207
+ function collectPluginAppRegistrations(definition, onComposerCustomizationRejected = (reason) => console.warn(reason)) {
208
+ const collected = {
209
+ homepageSections: [],
210
+ settingsSections: [],
211
+ navPanels: [],
212
+ threadPanelActions: [],
213
+ newThreadPanelActions: [],
214
+ composerCustomizations: [],
215
+ pendingInteractions: [],
216
+ sidebarFooterActions: [],
217
+ threadLists: [],
218
+ threadHeaderActions: [],
219
+ fileOpeners: [],
220
+ sourceCodeRenderers: [],
221
+ diffRenderers: [],
222
+ messageDirectives: [],
223
+ messageActions: [],
224
+ providerIcons: [],
225
+ contentScripts: []
226
+ };
227
+ const seenIds = {
228
+ homepageSection: /* @__PURE__ */ new Set(),
229
+ settingsSection: /* @__PURE__ */ new Set(),
230
+ navPanel: /* @__PURE__ */ new Set(),
231
+ threadPanelAction: /* @__PURE__ */ new Set(),
232
+ newThreadPanelAction: /* @__PURE__ */ new Set(),
233
+ composerCustomization: /* @__PURE__ */ new Set(),
234
+ pendingInteraction: /* @__PURE__ */ new Set(),
235
+ sidebarFooterAction: /* @__PURE__ */ new Set(),
236
+ threadList: /* @__PURE__ */ new Set(),
237
+ threadHeaderAction: /* @__PURE__ */ new Set(),
238
+ fileOpener: /* @__PURE__ */ new Set(),
239
+ sourceCodeRenderer: /* @__PURE__ */ new Set(),
240
+ diffRenderer: /* @__PURE__ */ new Set(),
241
+ messageDirective: /* @__PURE__ */ new Set(),
242
+ messageAction: /* @__PURE__ */ new Set(),
243
+ providerIcon: /* @__PURE__ */ new Set(),
244
+ contentScript: /* @__PURE__ */ new Set()
245
+ };
246
+ definition.setup({
247
+ slots: {
248
+ homepageSection(registration) {
249
+ const kind = "slots.homepageSection";
250
+ const id = requireSlotId(kind, registration?.id);
251
+ requireUniqueId(kind, seenIds.homepageSection, id);
252
+ collected.homepageSections.push({
253
+ id,
254
+ title: requireNonEmptyString(kind, "title", registration.title),
255
+ component: requireComponent(kind, registration.component)
256
+ });
257
+ },
258
+ settingsSection(registration) {
259
+ const kind = "slots.settingsSection";
260
+ const id = requireSlotId(kind, registration?.id);
261
+ requireUniqueId(kind, seenIds.settingsSection, id);
262
+ const title = requireOptionalString(kind, "title", registration.title);
263
+ const description = requireOptionalString(
264
+ kind,
265
+ "description",
266
+ registration.description
267
+ );
268
+ collected.settingsSections.push({
269
+ id,
270
+ ...title !== void 0 ? { title } : {},
271
+ ...description !== void 0 ? { description } : {},
272
+ component: requireComponent(kind, registration.component)
273
+ });
274
+ },
275
+ navPanel(registration) {
276
+ const kind = "slots.navPanel";
277
+ const id = requireSlotId(kind, registration?.id);
278
+ requireUniqueId(kind, seenIds.navPanel, id);
279
+ const path = requireNonEmptyString(kind, "path", registration.path);
280
+ if (!PLUGIN_SLOT_ID_PATTERN.test(path)) {
281
+ throw new Error(
282
+ `${kind}: "path" must match ${String(PLUGIN_SLOT_ID_PATTERN)} (it becomes a URL segment), got ${JSON.stringify(path)}`
283
+ );
284
+ }
285
+ if (registration.headerContent !== void 0 && typeof registration.headerContent !== "function") {
286
+ throw new Error(
287
+ `${kind}: "headerContent" must be a React component function when set`
288
+ );
289
+ }
290
+ if (registration.experimental_sidebarAccessory !== void 0 && typeof registration.experimental_sidebarAccessory !== "function") {
291
+ throw new Error(
292
+ `${kind}: "experimental_sidebarAccessory" must be a React component function when set`
293
+ );
294
+ }
295
+ const experimentalFixedTabs = (() => {
296
+ if (registration.experimental_fixedTabs === void 0) return [];
297
+ if (!Array.isArray(registration.experimental_fixedTabs)) {
298
+ throw new Error(
299
+ `${kind}: "experimental_fixedTabs" must be an array when set`
300
+ );
301
+ }
302
+ const seenFixedTabIds = /* @__PURE__ */ new Set();
303
+ return registration.experimental_fixedTabs.map((value, index) => {
304
+ const fixedTabKind = `${kind}.experimental_fixedTabs[${index}]`;
305
+ const fixedTab = value;
306
+ const id2 = requireSlotId(fixedTabKind, fixedTab?.id);
307
+ requireUniqueId(fixedTabKind, seenFixedTabIds, id2);
308
+ const layout = fixedTab?.layout;
309
+ if (layout !== void 0 && layout !== "padded" && layout !== "flush") {
310
+ throw new Error(
311
+ `${fixedTabKind}: "layout" must be "padded" or "flush" when set`
312
+ );
313
+ }
314
+ return {
315
+ id: id2,
316
+ title: requireNonEmptyString(
317
+ fixedTabKind,
318
+ "title",
319
+ fixedTab?.title
320
+ ),
321
+ icon: requireNonEmptyString(
322
+ fixedTabKind,
323
+ "icon",
324
+ fixedTab?.icon
325
+ ),
326
+ component: requireComponent(fixedTabKind, fixedTab?.component),
327
+ ...layout === void 0 ? {} : { layout }
328
+ };
329
+ });
330
+ })();
331
+ collected.navPanels.push({
332
+ id,
333
+ title: requireNonEmptyString(kind, "title", registration.title),
334
+ icon: requireNonEmptyString(kind, "icon", registration.icon),
335
+ path,
336
+ component: requireComponent(kind, registration.component),
337
+ ...experimentalFixedTabs.length > 0 ? { experimental_fixedTabs: experimentalFixedTabs } : {},
338
+ ...registration.experimental_sidebarAccessory !== void 0 ? {
339
+ experimental_sidebarAccessory: registration.experimental_sidebarAccessory
340
+ } : {},
341
+ ...registration.headerContent !== void 0 ? { headerContent: registration.headerContent } : {}
342
+ });
343
+ },
344
+ threadPanelAction(registration) {
345
+ const kind = "slots.threadPanelAction";
346
+ const id = requireSlotId(kind, registration?.id);
347
+ requireUniqueId(kind, seenIds.threadPanelAction, id);
348
+ if (registration.run !== void 0 && typeof registration.run !== "function") {
349
+ throw new Error(`${kind}: "run" must be a function when set`);
350
+ }
351
+ if (registration.layout !== void 0 && registration.layout !== "padded" && registration.layout !== "flush") {
352
+ throw new Error(`${kind}: "layout" must be "padded" or "flush"`);
353
+ }
354
+ collected.threadPanelActions.push({
355
+ id,
356
+ title: requireNonEmptyString(kind, "title", registration.title),
357
+ ...registration.icon !== void 0 ? {
358
+ icon: requireNonEmptyString(kind, "icon", registration.icon)
359
+ } : {},
360
+ component: requireComponent(kind, registration.component),
361
+ ...registration.layout !== void 0 ? { layout: registration.layout } : {},
362
+ ...registration.run !== void 0 ? { run: registration.run } : {}
363
+ });
364
+ },
365
+ experimental_newThreadPanelAction(registration) {
366
+ const kind = "slots.experimental_newThreadPanelAction";
367
+ const id = requireSlotId(kind, registration?.id);
368
+ requireUniqueId(kind, seenIds.newThreadPanelAction, id);
369
+ if (registration.run !== void 0 && typeof registration.run !== "function") {
370
+ throw new Error(`${kind}: "run" must be a function when set`);
371
+ }
372
+ if (registration.layout !== void 0 && registration.layout !== "padded" && registration.layout !== "flush") {
373
+ throw new Error(`${kind}: "layout" must be "padded" or "flush"`);
374
+ }
375
+ collected.newThreadPanelActions.push({
376
+ id,
377
+ title: requireNonEmptyString(kind, "title", registration.title),
378
+ ...registration.icon !== void 0 ? {
379
+ icon: requireNonEmptyString(kind, "icon", registration.icon)
380
+ } : {},
381
+ component: requireComponent(kind, registration.component),
382
+ ...registration.layout !== void 0 ? { layout: registration.layout } : {},
383
+ ...registration.run !== void 0 ? { run: registration.run } : {}
384
+ });
385
+ },
386
+ pendingInteraction(registration) {
387
+ const kind = "slots.pendingInteraction";
388
+ const id = requireSlotId(kind, registration?.id);
389
+ requireUniqueId(kind, seenIds.pendingInteraction, id);
390
+ collected.pendingInteractions.push({
391
+ id,
392
+ component: requireComponent(kind, registration.component)
393
+ });
394
+ },
395
+ sidebarFooterAction(registration) {
396
+ const kind = "slots.sidebarFooterAction";
397
+ const id = requireSlotId(kind, registration?.id);
398
+ requireUniqueId(kind, seenIds.sidebarFooterAction, id);
399
+ if (typeof registration.run !== "function") {
400
+ throw new Error(`${kind}: "run" must be a function`);
401
+ }
402
+ collected.sidebarFooterActions.push({
403
+ id,
404
+ title: requireNonEmptyString(kind, "title", registration.title),
405
+ icon: requireNonEmptyString(kind, "icon", registration.icon),
406
+ run: registration.run
407
+ });
408
+ },
409
+ experimental_threadList(registration) {
410
+ const kind = "slots.experimental_threadList";
411
+ const id = requireSlotId(kind, registration?.id);
412
+ requireUniqueId(kind, seenIds.threadList, id);
413
+ const description = requireOptionalString(
414
+ kind,
415
+ "description",
416
+ registration.description
417
+ );
418
+ collected.threadLists.push({
419
+ id,
420
+ title: requireNonEmptyString(kind, "title", registration.title),
421
+ ...description !== void 0 ? { description } : {},
422
+ component: requireComponent(kind, registration.component)
423
+ });
424
+ },
425
+ experimental_threadHeaderAction(registration) {
426
+ const kind = "slots.experimental_threadHeaderAction";
427
+ const id = requireSlotId(kind, registration?.id);
428
+ requireUniqueId(kind, seenIds.threadHeaderAction, id);
429
+ collected.threadHeaderActions.push({
430
+ id,
431
+ title: requireNonEmptyString(kind, "title", registration.title),
432
+ component: requireComponent(kind, registration.component)
433
+ });
434
+ },
435
+ fileOpener(registration) {
436
+ const kind = "slots.fileOpener";
437
+ const id = requireSlotId(kind, registration?.id);
438
+ requireUniqueId(kind, seenIds.fileOpener, id);
439
+ const rawExtensions = registration?.extensions;
440
+ if (!Array.isArray(rawExtensions) || rawExtensions.length === 0) {
441
+ throw new Error(
442
+ `${kind}: "extensions" must be a non-empty array of lowercase extensions without the dot`
443
+ );
444
+ }
445
+ const extensions = rawExtensions.map((extension) => {
446
+ if (typeof extension !== "string" || !/^[a-z0-9]+$/.test(extension)) {
447
+ throw new Error(
448
+ `${kind}: extensions must be lowercase alphanumerics without the dot, got ${JSON.stringify(extension)}`
449
+ );
450
+ }
451
+ return extension;
452
+ });
453
+ collected.fileOpeners.push({
454
+ id,
455
+ title: requireNonEmptyString(kind, "title", registration.title),
456
+ extensions,
457
+ component: requireComponent(kind, registration.component)
458
+ });
459
+ },
460
+ experimental_sourceCodeRenderer(registration) {
461
+ const kind = "slots.experimental_sourceCodeRenderer";
462
+ const id = requireSlotId(kind, registration?.id);
463
+ requireUniqueId(kind, seenIds.sourceCodeRenderer, id);
464
+ const description = requireOptionalString(
465
+ kind,
466
+ "description",
467
+ registration.description
468
+ );
469
+ collected.sourceCodeRenderers.push({
470
+ id,
471
+ title: requireNonEmptyString(kind, "title", registration.title),
472
+ ...description !== void 0 ? { description } : {},
473
+ component: requireComponent(kind, registration.component)
474
+ });
475
+ },
476
+ experimental_diffRenderer(registration) {
477
+ const kind = "slots.experimental_diffRenderer";
478
+ const id = requireSlotId(kind, registration?.id);
479
+ requireUniqueId(kind, seenIds.diffRenderer, id);
480
+ const description = requireOptionalString(
481
+ kind,
482
+ "description",
483
+ registration.description
484
+ );
485
+ collected.diffRenderers.push({
486
+ id,
487
+ title: requireNonEmptyString(kind, "title", registration.title),
488
+ ...description !== void 0 ? { description } : {},
489
+ component: requireComponent(kind, registration.component)
490
+ });
491
+ },
492
+ messageDirective(registration) {
493
+ const kind = "slots.messageDirective";
494
+ const id = requireMessageDirectiveId(kind, registration?.id);
495
+ requireUniqueId(kind, seenIds.messageDirective, id);
496
+ collected.messageDirectives.push({
497
+ id,
498
+ component: requireComponent(kind, registration.component)
499
+ });
500
+ },
501
+ messageAction(registration) {
502
+ const kind = "slots.messageAction";
503
+ const id = requireSlotId(kind, registration?.id);
504
+ requireUniqueId(kind, seenIds.messageAction, id);
505
+ if (typeof registration.run !== "function") {
506
+ throw new Error(`${kind}: "run" must be a function`);
507
+ }
508
+ collected.messageActions.push({
509
+ id,
510
+ title: requireNonEmptyString(kind, "title", registration.title),
511
+ ...registration.icon !== void 0 ? {
512
+ icon: requireNonEmptyString(kind, "icon", registration.icon)
513
+ } : {},
514
+ run: registration.run
515
+ });
516
+ },
517
+ experimental_providerIcon(registration) {
518
+ const kind = "slots.experimental_providerIcon";
519
+ const providerId = requireProviderId(kind, registration?.providerId);
520
+ requireUniqueId(kind, seenIds.providerIcon, providerId);
521
+ collected.providerIcons.push({
522
+ providerId,
523
+ icon: requireComponent(kind, registration.icon)
524
+ });
525
+ }
526
+ },
527
+ composer: {
528
+ customize(registration) {
529
+ const customization = collectComposerCustomization(
530
+ registration,
531
+ seenIds.composerCustomization,
532
+ onComposerCustomizationRejected
533
+ );
534
+ if (customization !== null) {
535
+ collected.composerCustomizations.push(customization);
536
+ }
537
+ }
538
+ },
539
+ contentScripts: {
540
+ register(registration) {
541
+ const kind = "contentScripts.register";
542
+ const id = requireSlotId(kind, registration?.id);
543
+ requireUniqueId(kind, seenIds.contentScript, id);
544
+ if (typeof registration.mount !== "function") {
545
+ throw new Error(`${kind}: "mount" must be a function`);
546
+ }
547
+ collected.contentScripts.push({ id, mount: registration.mount });
548
+ }
549
+ }
550
+ });
551
+ return collected;
552
+ }
553
+ export {
554
+ collectPluginAppRegistrations
555
+ };