@proveanything/smartlinks 2.0.52 → 2.0.54

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.
@@ -379,21 +379,16 @@ export declare namespace appConfiguration {
379
379
  * ```typescript
380
380
  * // Fetch all widget data for a collection
381
381
  * const { apps } = await Api.AppConfiguration.getWidgets(collectionId);
382
- * // Returns: [{ appId, manifestUrl, manifest, bundleSource, bundleCss }, ...]
383
- *
384
- * // Convert bundle source to dynamic imports
385
- * for (const app of apps) {
386
- * const blob = new Blob([app.bundleSource], { type: 'application/javascript' });
387
- * const blobUrl = URL.createObjectURL(blob);
388
- * const widgetModule = await import(blobUrl);
389
- *
390
- * // Inject CSS if present
391
- * if (app.bundleCss) {
392
- * const styleTag = document.createElement('style');
393
- * styleTag.textContent = app.bundleCss;
394
- * document.head.appendChild(styleTag);
395
- * }
396
- * }
382
+ * // Returns: [{ appId, manifest, widget, container, admin }, ...]
383
+ * // widget / container: { js: { umd?, esm? }, css?, source?, styles? }
384
+ * // source / styles - the bundle inlined (when small enough); js / css - URLs otherwise.
385
+ * // admin - URL of the app's app.admin.json (setup / tunable options), or null.
386
+ * // Bundles don't ship React: they resolve shared libraries from the HOST's globals
387
+ * // (host-dependency-contract.md), so loading one is a host's job.
388
+ * //
389
+ * // Inside a host (Portal / Hub), an app showing ANOTHER app's widget should not load
390
+ * // bundles itself - use the host-provided EmbeddedWidget prop:
391
+ * // <EmbeddedWidget appId="raffle" size="standard" /> (docs/widgets.md)
397
392
  *
398
393
  * // Force refresh all widgets
399
394
  * const { apps } = await Api.AppConfiguration.getWidgets(collectionId, { force: true });
@@ -482,21 +482,16 @@ export var appConfiguration;
482
482
  * ```typescript
483
483
  * // Fetch all widget data for a collection
484
484
  * const { apps } = await Api.AppConfiguration.getWidgets(collectionId);
485
- * // Returns: [{ appId, manifestUrl, manifest, bundleSource, bundleCss }, ...]
486
- *
487
- * // Convert bundle source to dynamic imports
488
- * for (const app of apps) {
489
- * const blob = new Blob([app.bundleSource], { type: 'application/javascript' });
490
- * const blobUrl = URL.createObjectURL(blob);
491
- * const widgetModule = await import(blobUrl);
492
- *
493
- * // Inject CSS if present
494
- * if (app.bundleCss) {
495
- * const styleTag = document.createElement('style');
496
- * styleTag.textContent = app.bundleCss;
497
- * document.head.appendChild(styleTag);
498
- * }
499
- * }
485
+ * // Returns: [{ appId, manifest, widget, container, admin }, ...]
486
+ * // widget / container: { js: { umd?, esm? }, css?, source?, styles? }
487
+ * // source / styles - the bundle inlined (when small enough); js / css - URLs otherwise.
488
+ * // admin - URL of the app's app.admin.json (setup / tunable options), or null.
489
+ * // Bundles don't ship React: they resolve shared libraries from the HOST's globals
490
+ * // (host-dependency-contract.md), so loading one is a host's job.
491
+ * //
492
+ * // Inside a host (Portal / Hub), an app showing ANOTHER app's widget should not load
493
+ * // bundles itself - use the host-provided EmbeddedWidget prop:
494
+ * // <EmbeddedWidget appId="raffle" size="standard" /> (docs/widgets.md)
500
495
  *
501
496
  * // Force refresh all widgets
502
497
  * const { apps } = await Api.AppConfiguration.getWidgets(collectionId, { force: true });
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.52 | Generated: 2026-10-11T13:21:58.062Z
3
+ Version: 2.0.54 | Generated: 2026-10-12T17:51:26.609Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -9744,9 +9744,26 @@ interface SmartLinksWidgetProps {
9744
9744
  size?: 'compact' | 'standard' | 'large'
9745
9745
  lang?: string
9746
9746
  translations?: Record<string, string>
9747
+ EmbeddedWidget?: EmbeddedWidgetComponent
9747
9748
  }
9748
9749
  ```
9749
9750
 
9751
+ **EmbeddedWidgetProps** (interface)
9752
+ ```typescript
9753
+ interface EmbeddedWidgetProps {
9754
+ appId: string
9755
+ component?: string
9756
+ size?: 'compact' | 'standard' | 'large'
9757
+ widgetId?: string
9758
+ props?: Record<string, unknown>
9759
+ className?: string
9760
+ fallback?: unknown
9761
+ loading?: unknown
9762
+ }
9763
+ ```
9764
+
9765
+ **EmbeddedWidgetComponent** = `(props: EmbeddedWidgetProps) => any`
9766
+
9750
9767
  ### appConfiguration (api)
9751
9768
 
9752
9769
  **AppConfigOptions** (type)
@@ -10380,7 +10397,7 @@ Delete a keyed data item by ID within a scope. Requires admin authentication. ``
10380
10397
 
10381
10398
  **getWidgets**(collectionId: string,
10382
10399
  options?: GetCollectionWidgetsOptions) → `Promise<CollectionWidgetsResponse>`
10383
- Fetches ALL widget data (manifests + bundle files) for a collection in one call. Returns everything needed to render widgets with zero additional requests. This solves N+1 query problems by fetching manifests, JavaScript bundles, and CSS files in parallel on the server. ```typescript // Fetch all widget data for a collection const { apps } = await Api.AppConfiguration.getWidgets(collectionId); // Returns: [{ appId, manifestUrl, manifest, bundleSource, bundleCss }, ...] // Convert bundle source to dynamic imports for (const app of apps) { const blob = new Blob([app.bundleSource], { type: 'application/javascript' }); const blobUrl = URL.createObjectURL(blob); const widgetModule = await import(blobUrl); // Inject CSS if present if (app.bundleCss) { const styleTag = document.createElement('style'); styleTag.textContent = app.bundleCss; document.head.appendChild(styleTag); } } // Force refresh all widgets const { apps } = await Api.AppConfiguration.getWidgets(collectionId, { force: true }); ```
10400
+ Fetches ALL widget data (manifests + bundle files) for a collection in one call. Returns everything needed to render widgets with zero additional requests. This solves N+1 query problems by fetching manifests, JavaScript bundles, and CSS files in parallel on the server. ```typescript // Fetch all widget data for a collection const { apps } = await Api.AppConfiguration.getWidgets(collectionId); // Returns: [{ appId, manifest, widget, container, admin }, ...] // widget / container: { js: { umd?, esm? }, css?, source?, styles? } // source / styles - the bundle inlined (when small enough); js / css - URLs otherwise. // admin - URL of the app's app.admin.json (setup / tunable options), or null. // Bundles don't ship React: they resolve shared libraries from the HOST's globals // (host-dependency-contract.md), so loading one is a host's job. // // Inside a host (Portal / Hub), an app showing ANOTHER app's widget should not load // bundles itself - use the host-provided EmbeddedWidget prop: // <EmbeddedWidget appId="raffle" size="standard" /> (docs/widgets.md) // Force refresh all widgets const { apps } = await Api.AppConfiguration.getWidgets(collectionId, { force: true }); ```
10384
10401
 
10385
10402
  **resolveFeature**(system: SystemBlock | undefined, flag: string) → `boolean`
10386
10403
  Resolve whether a feature flag is on, applying the `accountType` default: `enterprise` accounts default every flag to **on** unless `features[flag]` is explicitly `false`; `standard` accounts (or missing `accountType`) default every flag to **off** unless `features[flag]` is explicitly `true`. An explicit value always wins over the default either way. This is the same logic `isFeatureEnabled()` / `isFeatureEnabledSync()` use internally — call those instead in normal code. `system.features` and `accountType` are public data (no admin-only feature-check path exists), so there's no reason to fetch differently for an admin surface. This is exposed separately only for the rare case where you already have a `system` block in hand from somewhere other than `getAppConfig()` (e.g. server-rendered props) and want to resolve a flag from it directly. ```typescript const enabled = appConfiguration.resolveFeature(someSystemBlock, 'custom_domain'); ```
@@ -226,6 +226,13 @@ All standard widget props apply:
226
226
 
227
227
  ---
228
228
 
229
+ ### Showing another app's widget
230
+
231
+ A container can show another installed app's widget inline with the host-provided `EmbeddedWidget` prop:
232
+ `<EmbeddedWidget appId="raffle" size="standard" />`. The host loads it, isolates it and contains any crash.
233
+ See [widgets.md → Using another app's widget](widgets.md#using-another-apps-widget). Don't hand-roll a
234
+ widget loader.
235
+
229
236
  ## Cross-App Navigation
230
237
 
231
238
  Containers support the same **structured navigation requests** as widgets. When a container needs to navigate to another app within the portal, it emits a `NavigationRequest` via the `onNavigate` callback. The portal orchestrator interprets the request, preserves hierarchy context, and performs the navigation.
@@ -146,6 +146,7 @@ interface SmartLinksWidgetProps {
146
146
  | `size` | `string?` | Size hint: 'compact', 'standard', or 'large' |
147
147
  | `lang` | `string?` | Language code (e.g., 'en', 'de', 'fr') |
148
148
  | `translations` | `object?` | Translation overrides |
149
+ | `EmbeddedWidget` | `component?` | Host-provided: show another app's widget inside yours — see [Using another app's widget](#using-another-apps-widget) |
149
150
 
150
151
  ---
151
152
 
@@ -557,6 +558,56 @@ If `VITE_ENABLE_WIDGETS` is not set to `true`, the build produces a minimal stub
557
558
 
558
559
  ---
559
560
 
561
+ ## Using another app's widget
562
+
563
+ Your container, page or widget can show **another installed app's widget** inline (a raffle entry, a
564
+ donate button, a loyalty card), natively, with no iframe. **The host does the work.** Portal and Hub pass
565
+ every app they mount an `EmbeddedWidget` component:
566
+
567
+ ```tsx
568
+ export function EventGuide({ EmbeddedWidget, ...props }: SmartLinksContainerProps) {
569
+ return (
570
+ <section>
571
+ <h2>Win a signed shirt</h2>
572
+ {EmbeddedWidget ? (
573
+ <EmbeddedWidget appId="raffle" size="standard" fallback={null} />
574
+ ) : null}
575
+ </section>
576
+ );
577
+ }
578
+ ```
579
+
580
+ | Prop | What it does |
581
+ |---|---|
582
+ | `appId` | The app whose widget to show, as installed on this collection. |
583
+ | `component` | Which of its widget components (`widgets.components[].name` in its manifest). Default: the first. |
584
+ | `size` | `'compact' \| 'standard' \| 'large'` — a hint the widget may honour. Default `standard`. |
585
+ | `widgetId` | A configured instance, for apps with widget instances (see [Widget Instance Resolution](#widget-instance-resolution)). |
586
+ | `props` | Extra props for the widget: its documented props and settings. |
587
+ | `fallback` / `loading` | What to show if it can't be shown / while it loads. Default: nothing. |
588
+
589
+ What the host does for you:
590
+ - Loads the widget through its own bundle cache: the same code that renders the portal's widgets. It
591
+ provides the shared libraries (React, the SDK, the router, …) and picks the component.
592
+ - Gives it the same context you have: collection, product, proof, user, language, and navigation
593
+ (`onNavigate` goes straight to the portal, so the widget's links work as they do anywhere else).
594
+ - Mounts it in **its own router**, so it never touches your routes. Don't wrap it in a router.
595
+ - Contains any crash: a broken widget renders `fallback`, never takes down your page.
596
+ - Renders `fallback` when the app isn't installed on this collection, exports no widget, or isn't allowed.
597
+
598
+ **What can be embedded:** first-party SmartLinks widgets (app IDs without a developer handle), and your
599
+ own publisher's apps (`acme--raffle` from `acme--events`). The host enforces this; anything else renders
600
+ `fallback`, with a console warning. An app can't embed itself, and nesting stops two levels deep.
601
+
602
+ **Don't build your own loader** (getWidgets → evaluate the bundle → shared globals → require shim). Every
603
+ copy drifts and breaks; `EmbeddedWidget` is absent outside a host, so render nothing there.
604
+
605
+ **Finding out what's available:** in Forge, `forge-widgets catalog` lists the widgets you can embed
606
+ (apps installed on the preview's collection), and `forge-widgets describe <app>` gives a widget's
607
+ components, sizes, props, settings and setup options.
608
+
609
+ ---
610
+
560
611
  ## Parent Integration
561
612
 
562
613
  ### Dynamic Import
package/dist/openapi.yaml CHANGED
@@ -30830,10 +30830,36 @@ components:
30830
30830
  type: object
30831
30831
  additionalProperties:
30832
30832
  type: string
30833
+ EmbeddedWidget:
30834
+ $ref: "#/components/schemas/EmbeddedWidgetComponent"
30833
30835
  required:
30834
30836
  - collectionId
30835
30837
  - appId
30836
30838
  - SL
30839
+ EmbeddedWidgetProps:
30840
+ type: object
30841
+ properties:
30842
+ appId:
30843
+ type: string
30844
+ component:
30845
+ type: string
30846
+ size:
30847
+ type: string
30848
+ enum:
30849
+ - compact
30850
+ - standard
30851
+ - large
30852
+ widgetId:
30853
+ type: string
30854
+ props:
30855
+ type: object
30856
+ additionalProperties: true
30857
+ className:
30858
+ type: string
30859
+ fallback: {}
30860
+ loading: {}
30861
+ required:
30862
+ - appId
30837
30863
  AppConfigOptions:
30838
30864
  type: object
30839
30865
  properties:
@@ -83,4 +83,32 @@ export interface SmartLinksWidgetProps {
83
83
  lang?: string;
84
84
  /** Translation key overrides */
85
85
  translations?: Record<string, string>;
86
+ /**
87
+ * Show ANOTHER app's widget inside this app, rendered by the host (Portal / Hub):
88
+ * `<EmbeddedWidget appId="raffle" size="standard" widgetId="…" />`. The host loads it, gives it the
89
+ * same context you have, keeps it in its own router and contains any crash. Absent outside a host.
90
+ * Only first-party widgets and your own publisher's apps can be embedded. See docs/widgets.md
91
+ * "Using another app's widget".
92
+ */
93
+ EmbeddedWidget?: EmbeddedWidgetComponent;
94
+ }
95
+ /** Props of the host-provided `EmbeddedWidget` (see {@link SmartLinksWidgetProps.EmbeddedWidget}). */
96
+ export interface EmbeddedWidgetProps {
97
+ /** The app whose widget to show, as installed on this collection. */
98
+ appId: string;
99
+ /** Which of its widget components (its manifest `widgets.components[].name`). Default: the first. */
100
+ component?: string;
101
+ /** Size hint the widget may honour. */
102
+ size?: 'compact' | 'standard' | 'large';
103
+ /** A configured instance of the widget (apps with widget instances — see "Widget Instance Resolution"). */
104
+ widgetId?: string;
105
+ /** Extra props for the widget: its documented props / settings. */
106
+ props?: Record<string, unknown>;
107
+ className?: string;
108
+ /** Shown when the widget can't be shown (not installed, no widget, not allowed, failed). Default: nothing. */
109
+ fallback?: unknown;
110
+ /** Shown while it loads. Default: nothing. */
111
+ loading?: unknown;
86
112
  }
113
+ /** A React component taking {@link EmbeddedWidgetProps} (typed loosely: the SDK doesn't depend on React). */
114
+ export type EmbeddedWidgetComponent = (props: EmbeddedWidgetProps) => any;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.52 | Generated: 2026-10-11T13:21:58.062Z
3
+ Version: 2.0.54 | Generated: 2026-10-12T17:51:26.609Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -9744,9 +9744,26 @@ interface SmartLinksWidgetProps {
9744
9744
  size?: 'compact' | 'standard' | 'large'
9745
9745
  lang?: string
9746
9746
  translations?: Record<string, string>
9747
+ EmbeddedWidget?: EmbeddedWidgetComponent
9747
9748
  }
9748
9749
  ```
9749
9750
 
9751
+ **EmbeddedWidgetProps** (interface)
9752
+ ```typescript
9753
+ interface EmbeddedWidgetProps {
9754
+ appId: string
9755
+ component?: string
9756
+ size?: 'compact' | 'standard' | 'large'
9757
+ widgetId?: string
9758
+ props?: Record<string, unknown>
9759
+ className?: string
9760
+ fallback?: unknown
9761
+ loading?: unknown
9762
+ }
9763
+ ```
9764
+
9765
+ **EmbeddedWidgetComponent** = `(props: EmbeddedWidgetProps) => any`
9766
+
9750
9767
  ### appConfiguration (api)
9751
9768
 
9752
9769
  **AppConfigOptions** (type)
@@ -10380,7 +10397,7 @@ Delete a keyed data item by ID within a scope. Requires admin authentication. ``
10380
10397
 
10381
10398
  **getWidgets**(collectionId: string,
10382
10399
  options?: GetCollectionWidgetsOptions) → `Promise<CollectionWidgetsResponse>`
10383
- Fetches ALL widget data (manifests + bundle files) for a collection in one call. Returns everything needed to render widgets with zero additional requests. This solves N+1 query problems by fetching manifests, JavaScript bundles, and CSS files in parallel on the server. ```typescript // Fetch all widget data for a collection const { apps } = await Api.AppConfiguration.getWidgets(collectionId); // Returns: [{ appId, manifestUrl, manifest, bundleSource, bundleCss }, ...] // Convert bundle source to dynamic imports for (const app of apps) { const blob = new Blob([app.bundleSource], { type: 'application/javascript' }); const blobUrl = URL.createObjectURL(blob); const widgetModule = await import(blobUrl); // Inject CSS if present if (app.bundleCss) { const styleTag = document.createElement('style'); styleTag.textContent = app.bundleCss; document.head.appendChild(styleTag); } } // Force refresh all widgets const { apps } = await Api.AppConfiguration.getWidgets(collectionId, { force: true }); ```
10400
+ Fetches ALL widget data (manifests + bundle files) for a collection in one call. Returns everything needed to render widgets with zero additional requests. This solves N+1 query problems by fetching manifests, JavaScript bundles, and CSS files in parallel on the server. ```typescript // Fetch all widget data for a collection const { apps } = await Api.AppConfiguration.getWidgets(collectionId); // Returns: [{ appId, manifest, widget, container, admin }, ...] // widget / container: { js: { umd?, esm? }, css?, source?, styles? } // source / styles - the bundle inlined (when small enough); js / css - URLs otherwise. // admin - URL of the app's app.admin.json (setup / tunable options), or null. // Bundles don't ship React: they resolve shared libraries from the HOST's globals // (host-dependency-contract.md), so loading one is a host's job. // // Inside a host (Portal / Hub), an app showing ANOTHER app's widget should not load // bundles itself - use the host-provided EmbeddedWidget prop: // <EmbeddedWidget appId="raffle" size="standard" /> (docs/widgets.md) // Force refresh all widgets const { apps } = await Api.AppConfiguration.getWidgets(collectionId, { force: true }); ```
10384
10401
 
10385
10402
  **resolveFeature**(system: SystemBlock | undefined, flag: string) → `boolean`
10386
10403
  Resolve whether a feature flag is on, applying the `accountType` default: `enterprise` accounts default every flag to **on** unless `features[flag]` is explicitly `false`; `standard` accounts (or missing `accountType`) default every flag to **off** unless `features[flag]` is explicitly `true`. An explicit value always wins over the default either way. This is the same logic `isFeatureEnabled()` / `isFeatureEnabledSync()` use internally — call those instead in normal code. `system.features` and `accountType` are public data (no admin-only feature-check path exists), so there's no reason to fetch differently for an admin surface. This is exposed separately only for the rare case where you already have a `system` block in hand from somewhere other than `getAppConfig()` (e.g. server-rendered props) and want to resolve a flag from it directly. ```typescript const enabled = appConfiguration.resolveFeature(someSystemBlock, 'custom_domain'); ```
@@ -226,6 +226,13 @@ All standard widget props apply:
226
226
 
227
227
  ---
228
228
 
229
+ ### Showing another app's widget
230
+
231
+ A container can show another installed app's widget inline with the host-provided `EmbeddedWidget` prop:
232
+ `<EmbeddedWidget appId="raffle" size="standard" />`. The host loads it, isolates it and contains any crash.
233
+ See [widgets.md → Using another app's widget](widgets.md#using-another-apps-widget). Don't hand-roll a
234
+ widget loader.
235
+
229
236
  ## Cross-App Navigation
230
237
 
231
238
  Containers support the same **structured navigation requests** as widgets. When a container needs to navigate to another app within the portal, it emits a `NavigationRequest` via the `onNavigate` callback. The portal orchestrator interprets the request, preserves hierarchy context, and performs the navigation.
package/docs/widgets.md CHANGED
@@ -146,6 +146,7 @@ interface SmartLinksWidgetProps {
146
146
  | `size` | `string?` | Size hint: 'compact', 'standard', or 'large' |
147
147
  | `lang` | `string?` | Language code (e.g., 'en', 'de', 'fr') |
148
148
  | `translations` | `object?` | Translation overrides |
149
+ | `EmbeddedWidget` | `component?` | Host-provided: show another app's widget inside yours — see [Using another app's widget](#using-another-apps-widget) |
149
150
 
150
151
  ---
151
152
 
@@ -557,6 +558,56 @@ If `VITE_ENABLE_WIDGETS` is not set to `true`, the build produces a minimal stub
557
558
 
558
559
  ---
559
560
 
561
+ ## Using another app's widget
562
+
563
+ Your container, page or widget can show **another installed app's widget** inline (a raffle entry, a
564
+ donate button, a loyalty card), natively, with no iframe. **The host does the work.** Portal and Hub pass
565
+ every app they mount an `EmbeddedWidget` component:
566
+
567
+ ```tsx
568
+ export function EventGuide({ EmbeddedWidget, ...props }: SmartLinksContainerProps) {
569
+ return (
570
+ <section>
571
+ <h2>Win a signed shirt</h2>
572
+ {EmbeddedWidget ? (
573
+ <EmbeddedWidget appId="raffle" size="standard" fallback={null} />
574
+ ) : null}
575
+ </section>
576
+ );
577
+ }
578
+ ```
579
+
580
+ | Prop | What it does |
581
+ |---|---|
582
+ | `appId` | The app whose widget to show, as installed on this collection. |
583
+ | `component` | Which of its widget components (`widgets.components[].name` in its manifest). Default: the first. |
584
+ | `size` | `'compact' \| 'standard' \| 'large'` — a hint the widget may honour. Default `standard`. |
585
+ | `widgetId` | A configured instance, for apps with widget instances (see [Widget Instance Resolution](#widget-instance-resolution)). |
586
+ | `props` | Extra props for the widget: its documented props and settings. |
587
+ | `fallback` / `loading` | What to show if it can't be shown / while it loads. Default: nothing. |
588
+
589
+ What the host does for you:
590
+ - Loads the widget through its own bundle cache: the same code that renders the portal's widgets. It
591
+ provides the shared libraries (React, the SDK, the router, …) and picks the component.
592
+ - Gives it the same context you have: collection, product, proof, user, language, and navigation
593
+ (`onNavigate` goes straight to the portal, so the widget's links work as they do anywhere else).
594
+ - Mounts it in **its own router**, so it never touches your routes. Don't wrap it in a router.
595
+ - Contains any crash: a broken widget renders `fallback`, never takes down your page.
596
+ - Renders `fallback` when the app isn't installed on this collection, exports no widget, or isn't allowed.
597
+
598
+ **What can be embedded:** first-party SmartLinks widgets (app IDs without a developer handle), and your
599
+ own publisher's apps (`acme--raffle` from `acme--events`). The host enforces this; anything else renders
600
+ `fallback`, with a console warning. An app can't embed itself, and nesting stops two levels deep.
601
+
602
+ **Don't build your own loader** (getWidgets → evaluate the bundle → shared globals → require shim). Every
603
+ copy drifts and breaks; `EmbeddedWidget` is absent outside a host, so render nothing there.
604
+
605
+ **Finding out what's available:** in Forge, `forge-widgets catalog` lists the widgets you can embed
606
+ (apps installed on the preview's collection), and `forge-widgets describe <app>` gives a widget's
607
+ components, sizes, props, settings and setup options.
608
+
609
+ ---
610
+
560
611
  ## Parent Integration
561
612
 
562
613
  ### Dynamic Import
package/openapi.yaml CHANGED
@@ -30830,10 +30830,36 @@ components:
30830
30830
  type: object
30831
30831
  additionalProperties:
30832
30832
  type: string
30833
+ EmbeddedWidget:
30834
+ $ref: "#/components/schemas/EmbeddedWidgetComponent"
30833
30835
  required:
30834
30836
  - collectionId
30835
30837
  - appId
30836
30838
  - SL
30839
+ EmbeddedWidgetProps:
30840
+ type: object
30841
+ properties:
30842
+ appId:
30843
+ type: string
30844
+ component:
30845
+ type: string
30846
+ size:
30847
+ type: string
30848
+ enum:
30849
+ - compact
30850
+ - standard
30851
+ - large
30852
+ widgetId:
30853
+ type: string
30854
+ props:
30855
+ type: object
30856
+ additionalProperties: true
30857
+ className:
30858
+ type: string
30859
+ fallback: {}
30860
+ loading: {}
30861
+ required:
30862
+ - appId
30837
30863
  AppConfigOptions:
30838
30864
  type: object
30839
30865
  properties:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.52",
3
+ "version": "2.0.54",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -262,13 +262,57 @@ if (meta.respectsHostTheme) {
262
262
  const text = readFileSync(bp, 'utf8');
263
263
  const routers = ['MemoryRouter', 'HashRouter', 'BrowserRouter'].filter((r) => new RegExp(`\\b${r}\\b`).test(text));
264
264
  if (!routers.length) { console.log(`${GREEN}✓${RESET} containers ${DIM}— no router of their own (the host's is used)${RESET}`); break; }
265
- if (/UNSAFE_LocationContext/.test(text)) { console.log(`${GREEN}✓${RESET} containers ${DIM}— own ${routers.join('/')} isolated from the host's router${RESET}`); break; }
265
+ if (/\bUNSAFE_LocationContext\b/.test(text)) { console.log(`${GREEN}✓${RESET} containers ${DIM}— own ${routers.join('/')} isolated from the host's router${RESET}`); break; }
266
266
  console.log(`${YELLOW}⚠${RESET} containers — ${routers.join('/')} without isolating it from the host's router`);
267
267
  warnings.push(`containers bundle renders its own ${routers.join('/')} but never resets the host's router context — inside an R5 host that throws "You cannot render a <Router> inside another <Router>". Wrap it in UNSAFE_LocationContext.Provider + UNSAFE_NavigationContext.Provider value={null} (docs/containers.md), or drop the router and use the host's.`);
268
268
  break;
269
269
  }
270
270
  }
271
271
 
272
+ // ---- showing another app's widget: use the host's EmbeddedWidget, for widgets it allows -----------
273
+ // Hosts (Portal, Hub) pass every app an `EmbeddedWidget` prop (docs/widgets.md → "Using another app's
274
+ // widget"). A hand-rolled loader (getWidgets → evaluate the bundle) drifts and breaks; and the host only
275
+ // renders first-party widgets and the same publisher's apps, so anything else silently shows nothing.
276
+ // Static scan of the source; WARN-only.
277
+ {
278
+ const ownId = String(manifest.meta?.appId || '');
279
+ const handleOf = (id) => { const i = id.indexOf('--'); return i > 0 ? id.slice(0, i) : null; };
280
+ const EXT = /\.(tsx|ts|jsx|js)$/;
281
+ const SKIP = new Set(['node_modules', 'dist', '.git', 'public']);
282
+ const files = [];
283
+ (function walkSrc(dir) {
284
+ let entries = [];
285
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
286
+ for (const e of entries) {
287
+ if (e.isDirectory()) { if (!SKIP.has(e.name)) walkSrc(join(dir, e.name)); }
288
+ else if (EXT.test(e.name)) files.push(join(dir, e.name));
289
+ }
290
+ })(existsSync(join(appDir, 'src')) ? join(appDir, 'src') : appDir);
291
+ const rel = (f) => (f.startsWith(appDir) ? f.slice(appDir.length + 1) : f);
292
+ const handRolled = [];
293
+ const blocked = new Set();
294
+ let embeds = 0;
295
+ for (const f of files) {
296
+ const text = readFileSync(f, 'utf8');
297
+ if (/\bgetWidgets\s*\(/.test(text) && /(new\s+Blob\s*\(|new\s+Function\s*\(|createRequireShim|import\s*\(\s*\/\*\s*@vite-ignore)/.test(text)) handRolled.push(rel(f));
298
+ for (const m of text.matchAll(/<EmbeddedWidget\b[^>]*?\bappId\s*=\s*\{?\s*["'`]([A-Za-z0-9._-]+)["'`]/g)) {
299
+ embeds++;
300
+ const id = m[1];
301
+ const owner = handleOf(id);
302
+ if (id === ownId || (owner !== null && owner !== handleOf(ownId))) blocked.add(id);
303
+ }
304
+ }
305
+ if (handRolled.length) {
306
+ console.log(`${YELLOW}⚠${RESET} embedded widgets — a hand-rolled widget loader in ${handRolled.slice(0, 3).join(', ')}`);
307
+ warnings.push(`${handRolled.join(', ')} load${handRolled.length === 1 ? 's' : ''} another app's widget bundle by hand (getWidgets + evaluating it). Use the host's EmbeddedWidget prop instead: <EmbeddedWidget appId="…" /> (docs/widgets.md → "Using another app's widget").`);
308
+ }
309
+ if (blocked.size) {
310
+ console.log(`${YELLOW}⚠${RESET} embedded widgets — the host won't show: ${[...blocked].join(', ')}`);
311
+ warnings.push(`EmbeddedWidget for ${[...blocked].join(', ')} will render nothing: hosts only show first-party widgets and the same publisher's apps${ownId ? ` (this app is "${ownId}")` : ''}, and never the app itself.`);
312
+ }
313
+ if (embeds && !handRolled.length && !blocked.size) console.log(`${GREEN}✓${RESET} embedded widgets ${DIM}— ${embeds} via the host's EmbeddedWidget${RESET}`);
314
+ }
315
+
272
316
  // ---- package.json scripts point at files that exist ------------------------------------------
273
317
  // An upgrade that copies a script LINE but not the script FILE breaks every build at that step
274
318
  // (e.g. `postbuild: node scripts/smartlinks-deploy.mjs` with no scripts/smartlinks-deploy.mjs).