@proveanything/smartlinks 2.0.5 → 2.0.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.
- package/dist/api/ai.d.ts +1 -1
- package/dist/api/ai.js +1 -1
- package/dist/api/analytics.d.ts +1 -1
- package/dist/api/analytics.js +1 -1
- package/dist/api/appConfiguration.d.ts +3 -3
- package/dist/api/appConfiguration.js +3 -3
- package/dist/api/appObjects.d.ts +1 -1
- package/dist/api/appObjects.js +1 -1
- package/dist/api/asset.d.ts +1 -1
- package/dist/api/asset.js +2 -2
- package/dist/api/async.d.ts +1 -1
- package/dist/api/async.js +1 -1
- package/dist/api/attestation.d.ts +1 -1
- package/dist/api/attestation.js +1 -1
- package/dist/api/attestations.d.ts +1 -1
- package/dist/api/attestations.js +1 -1
- package/dist/api/auth.d.ts +2 -2
- package/dist/api/auth.js +2 -2
- package/dist/api/authKit.d.ts +1 -1
- package/dist/api/authKit.js +1 -1
- package/dist/api/batch.d.ts +1 -1
- package/dist/api/batch.js +1 -1
- package/dist/api/broadcasts.d.ts +2 -2
- package/dist/api/broadcasts.js +1 -1
- package/dist/api/claimSet.d.ts +1 -1
- package/dist/api/claimSet.js +1 -1
- package/dist/api/collection.d.ts +1 -1
- package/dist/api/collection.js +1 -1
- package/dist/api/comms.d.ts +15 -15
- package/dist/api/comms.js +1 -1
- package/dist/api/config.d.ts +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/contact.d.ts +1 -1
- package/dist/api/contact.js +1 -1
- package/dist/api/containers.d.ts +1 -1
- package/dist/api/containers.js +1 -1
- package/dist/api/crate.d.ts +1 -1
- package/dist/api/crate.js +1 -1
- package/dist/api/facets.d.ts +1 -1
- package/dist/api/facets.js +1 -1
- package/dist/api/form.js +1 -1
- package/dist/api/http.js +1 -1
- package/dist/api/index.d.ts +46 -46
- package/dist/api/index.js +46 -46
- package/dist/api/integrations.d.ts +1 -1
- package/dist/api/integrations.js +1 -1
- package/dist/api/interactions.d.ts +1 -1
- package/dist/api/interactions.js +1 -1
- package/dist/api/jobs.d.ts +1 -1
- package/dist/api/jobs.js +1 -1
- package/dist/api/journeys.d.ts +1 -1
- package/dist/api/journeys.js +1 -1
- package/dist/api/journeysAnalytics.d.ts +1 -1
- package/dist/api/journeysAnalytics.js +1 -1
- package/dist/api/location.d.ts +1 -1
- package/dist/api/location.js +1 -1
- package/dist/api/lots.d.ts +1 -1
- package/dist/api/lots.js +1 -1
- package/dist/api/loyalty.d.ts +1 -1
- package/dist/api/loyalty.js +1 -1
- package/dist/api/navigation.d.ts +1 -1
- package/dist/api/navigation.js +1 -1
- package/dist/api/nfc.d.ts +1 -1
- package/dist/api/nfc.js +1 -1
- package/dist/api/order.d.ts +1 -1
- package/dist/api/order.js +1 -1
- package/dist/api/product.d.ts +1 -1
- package/dist/api/product.js +1 -1
- package/dist/api/products.d.ts +1 -1
- package/dist/api/products.js +1 -1
- package/dist/api/proof.d.ts +1 -1
- package/dist/api/proof.js +1 -1
- package/dist/api/qr.d.ts +1 -1
- package/dist/api/qr.js +1 -1
- package/dist/api/realtime.d.ts +1 -1
- package/dist/api/realtime.js +1 -1
- package/dist/api/research.d.ts +1 -1
- package/dist/api/research.js +1 -1
- package/dist/api/secrets.d.ts +1 -1
- package/dist/api/secrets.js +1 -1
- package/dist/api/segments.d.ts +1 -1
- package/dist/api/segments.js +1 -1
- package/dist/api/sequence.js +1 -1
- package/dist/api/tags.d.ts +1 -1
- package/dist/api/tags.js +1 -1
- package/dist/api/template.d.ts +1 -1
- package/dist/api/template.js +1 -1
- package/dist/api/translations.d.ts +1 -1
- package/dist/api/translations.js +2 -2
- package/dist/api/variant.d.ts +1 -1
- package/dist/api/variant.js +1 -1
- package/dist/containers/types.d.ts +1 -1
- package/dist/docs/API_SUMMARY.md +7 -7
- package/dist/docs/agent-tools.md +111 -0
- package/dist/docs/ai.md +14 -520
- package/dist/docs/analytics.md +41 -2
- package/dist/docs/app-data-storage.md +0 -38
- package/dist/docs/app-manifest.md +104 -7
- package/dist/docs/app-objects.md +0 -148
- package/dist/docs/app-records-pattern.md +2 -2
- package/dist/docs/building-react-components.md +6 -14
- package/dist/docs/caching.md +20 -21
- package/dist/docs/container-tracking.md +2 -0
- package/dist/docs/containers.md +14 -66
- package/dist/docs/deploying-apps.md +8 -3
- package/dist/docs/executor.md +4 -4
- package/dist/docs/host-dependency-contract.md +159 -0
- package/dist/docs/iframe-responder.md +308 -0
- package/dist/docs/item-context.md +0 -2
- package/dist/docs/manifests.md +3 -3
- package/dist/docs/mobile-admin-container.md +4 -4
- package/dist/docs/mpa.md +5 -5
- package/dist/docs/native-facade.md +1 -1
- package/dist/docs/overview.md +36 -15
- package/dist/docs/portal-back-button.md +2 -3
- package/dist/docs/sequences.md +1 -1
- package/dist/docs/server-functions.md +2 -3
- package/dist/docs/widgets.md +11 -69
- package/dist/http.d.ts +24 -8
- package/dist/http.js +32 -14
- package/dist/iframe.d.ts +2 -2
- package/dist/iframe.js +1 -1
- package/dist/iframeResponder.d.ts +7 -1
- package/dist/iframeResponder.js +45 -4
- package/dist/index.d.ts +30 -27
- package/dist/index.js +10 -8
- package/dist/mobile-admin/errors.d.ts +1 -1
- package/dist/mobile-admin/types.d.ts +2 -2
- package/dist/openapi.yaml +12 -0
- package/dist/shared-dependencies.d.ts +37 -0
- package/dist/shared-dependencies.js +79 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/translationCache.d.ts +1 -1
- package/dist/types/appManifest.d.ts +23 -0
- package/dist/types/broadcasts.d.ts +1 -1
- package/dist/types/collection.d.ts +2 -2
- package/dist/types/comms.d.ts +5 -5
- package/dist/types/contact.d.ts +1 -1
- package/dist/types/facets.d.ts +1 -1
- package/dist/types/iframeResponder.d.ts +3 -3
- package/dist/types/index.d.ts +44 -44
- package/dist/types/index.js +44 -44
- package/dist/types/interaction.d.ts +1 -1
- package/dist/types/itemContext.d.ts +1 -1
- package/dist/types/journeysAnalytics.d.ts +1 -1
- package/dist/types/navigation.d.ts +1 -1
- package/dist/types/product.d.ts +1 -1
- package/dist/types/proof.d.ts +1 -1
- package/dist/types/segments.d.ts +1 -1
- package/dist/types/widgets.d.ts +2 -2
- package/dist/utils/conditions.d.ts +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/dist/utils/paths.d.ts +4 -4
- package/docs/API_SUMMARY.md +7 -7
- package/docs/agent-tools.md +111 -0
- package/docs/ai.md +14 -520
- package/docs/analytics.md +41 -2
- package/docs/app-data-storage.md +0 -38
- package/docs/app-manifest.md +104 -7
- package/docs/app-objects.md +0 -148
- package/docs/app-records-pattern.md +2 -2
- package/docs/building-react-components.md +6 -14
- package/docs/caching.md +20 -21
- package/docs/container-tracking.md +2 -0
- package/docs/containers.md +14 -66
- package/docs/deploying-apps.md +8 -3
- package/docs/executor.md +4 -4
- package/docs/host-dependency-contract.md +159 -0
- package/docs/iframe-responder.md +308 -0
- package/docs/item-context.md +0 -2
- package/docs/mobile-admin-container.md +4 -4
- package/docs/mpa.md +5 -5
- package/docs/native-facade.md +1 -1
- package/docs/overview.md +36 -15
- package/docs/portal-back-button.md +2 -3
- package/docs/sequences.md +1 -1
- package/docs/server-functions.md +2 -3
- package/docs/widgets.md +11 -69
- package/openapi.yaml +12 -0
- package/package.json +17 -6
- package/scripts/doctor.mjs +171 -0
- package/docs/analytics-metadata-conventions.md +0 -88
- package/docs/iframe-streaming-parent-changes.md +0 -308
- package/docs/manifests.md +0 -204
package/docs/sequences.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sequences & claim-order allocation
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **SmartLinks SDK 2.x** (current `latest`). Install `@proveanything/smartlinks@^2`.
|
|
4
4
|
|
|
5
5
|
A **sequence** hands out a guaranteed-unique, monotonic number — `1, 2, 3, …` — and stamps it
|
|
6
6
|
onto a record. It's the primitive behind raffle tickets, "you're the Nth to claim", queue
|
package/docs/server-functions.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Server functions ("edge functions")
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
> then. Published under the npm `next` tag; `latest` remains 1.x.
|
|
3
|
+
> **SmartLinks SDK 2.x** (current `latest`). Part of the installable-app platform
|
|
4
|
+
> (author → register → install → run → test). Install `@proveanything/smartlinks@^2`.
|
|
6
5
|
|
|
7
6
|
A **server function** is arbitrary server-side JavaScript your app deploys directly into
|
|
8
7
|
SmartLinks. It runs on the SmartLinks servers — with access to the full SDK, to your app's
|
package/docs/widgets.md
CHANGED
|
@@ -14,7 +14,7 @@ Widgets are self-contained React components that:
|
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
16
|
┌─────────────────────────────────────────────────────────────────┐
|
|
17
|
-
│ Parent SmartLinks Portal (React
|
|
17
|
+
│ Parent SmartLinks Portal (React 19) │
|
|
18
18
|
│ │
|
|
19
19
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
20
20
|
│ │ Competition │ │ Music App │ │ Warranty │ │
|
|
@@ -53,53 +53,7 @@ Widgets are typically single-view components and don't need internal routing. If
|
|
|
53
53
|
|
|
54
54
|
### The `useAppContext()` Pattern
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
// src/hooks/useAppContext.ts
|
|
60
|
-
import { useContext, createContext, useMemo } from 'react';
|
|
61
|
-
import { useSearchParams } from 'react-router-dom';
|
|
62
|
-
|
|
63
|
-
export interface AppContextValue {
|
|
64
|
-
collectionId: string;
|
|
65
|
-
appId: string;
|
|
66
|
-
productId?: string;
|
|
67
|
-
proofId?: string;
|
|
68
|
-
pageId?: string;
|
|
69
|
-
lang?: string;
|
|
70
|
-
user?: { id: string; email: string; name?: string };
|
|
71
|
-
SL: typeof import('@proveanything/smartlinks');
|
|
72
|
-
onNavigate?: (request: any) => void;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const AppContext = createContext<AppContextValue | null>(null);
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Returns app context regardless of rendering mode.
|
|
79
|
-
* - Direct component mode: reads from AppContext (props)
|
|
80
|
-
* - Iframe mode: reads from URL search params
|
|
81
|
-
*/
|
|
82
|
-
export function useAppContext(): AppContextValue {
|
|
83
|
-
const ctx = useContext(AppContext);
|
|
84
|
-
|
|
85
|
-
// If context exists, we're in direct-component mode
|
|
86
|
-
if (ctx) return ctx;
|
|
87
|
-
|
|
88
|
-
// Otherwise, we're in iframe mode — read from URL params
|
|
89
|
-
const [searchParams] = useSearchParams();
|
|
90
|
-
const SL = (window as any).SL ?? require('@proveanything/smartlinks');
|
|
91
|
-
|
|
92
|
-
return useMemo(() => ({
|
|
93
|
-
collectionId: searchParams.get('collectionId') ?? '',
|
|
94
|
-
appId: searchParams.get('appId') ?? '',
|
|
95
|
-
productId: searchParams.get('productId') ?? undefined,
|
|
96
|
-
proofId: searchParams.get('proofId') ?? undefined,
|
|
97
|
-
pageId: searchParams.get('pageId') ?? undefined,
|
|
98
|
-
lang: searchParams.get('lang') ?? undefined,
|
|
99
|
-
SL,
|
|
100
|
-
}), [searchParams, SL]);
|
|
101
|
-
}
|
|
102
|
-
```
|
|
56
|
+
Widgets read their context through the shared **`useAppContext()`** hook so the same code works in both direct-component and iframe modes. The hook and `AppContext` provider are defined once — see **[building-react-components.md](building-react-components.md#the-useappcontext-pattern)** for the full implementation (don't re-define it per app).
|
|
103
57
|
|
|
104
58
|
**Usage in your widget:**
|
|
105
59
|
|
|
@@ -344,7 +298,7 @@ Declare support in `app.manifest.json`:
|
|
|
344
298
|
"files": {
|
|
345
299
|
"js": {
|
|
346
300
|
"umd": "dist/widgets.umd.js",
|
|
347
|
-
"esm": "dist/widgets.
|
|
301
|
+
"esm": "dist/widgets.esm.js"
|
|
348
302
|
},
|
|
349
303
|
"css": null
|
|
350
304
|
},
|
|
@@ -511,7 +465,7 @@ export { MyWidget } from './MyWidget';
|
|
|
511
465
|
// Update the manifest
|
|
512
466
|
export const WIDGET_MANIFEST = {
|
|
513
467
|
version: '1.0.0',
|
|
514
|
-
reactVersion: '
|
|
468
|
+
reactVersion: '19.x',
|
|
515
469
|
widgets: [
|
|
516
470
|
// ... existing widgets
|
|
517
471
|
{
|
|
@@ -570,7 +524,7 @@ The project includes a separate Vite config for building widgets:
|
|
|
570
524
|
# Build widgets only
|
|
571
525
|
vite build --config vite.config.widget.ts
|
|
572
526
|
|
|
573
|
-
# Output: dist/widgets.
|
|
527
|
+
# Output: dist/widgets.esm.js
|
|
574
528
|
```
|
|
575
529
|
|
|
576
530
|
### Build Configuration
|
|
@@ -582,23 +536,11 @@ The widget build:
|
|
|
582
536
|
- Minifies with esbuild for production
|
|
583
537
|
- Outputs to `/dist` alongside the main app (not a separate folder)
|
|
584
538
|
|
|
585
|
-
### Externalized
|
|
586
|
-
|
|
587
|
-
The widget bundle does **not** include these libraries—the parent app must provide them:
|
|
539
|
+
### Externalized dependencies
|
|
588
540
|
|
|
589
|
-
|
|
590
|
-
|---------|------------------|
|
|
591
|
-
| `react`, `react-dom` | Parent's React context |
|
|
592
|
-
| `@proveanything/smartlinks` | Passed via props as `SL` |
|
|
593
|
-
| `@proveanything/smartlinks-auth-ui` | Auth UI components (also available globally as `window.SmartlinksAuthUI`) |
|
|
594
|
-
| `tailwind-merge` | Utility for merging Tailwind classes |
|
|
595
|
-
| `clsx` | Utility for conditional class names |
|
|
596
|
-
| `class-variance-authority` | Utility for component variants |
|
|
541
|
+
The widget bundle does **not** include the host's shared libraries (React, the SDK, Radix, LiquidJS, …) — it **externalizes** them and resolves them from the host at runtime, so the bundle stays tiny and there's exactly one shared instance.
|
|
597
542
|
|
|
598
|
-
|
|
599
|
-
1. Reduces bundle size significantly
|
|
600
|
-
2. Removes JSDoc comments that inflate the bundle
|
|
601
|
-
3. Ensures consistent behavior with parent's versions
|
|
543
|
+
**The canonical, versioned list is the shared-dependency contract — don't hand-maintain your own here.** Import it from the SDK (`SHARED_DEPENDENCY_SPECIFIERS`) for your build's `external` list, and see **[host-dependency-contract.md](host-dependency-contract.md)** for the full table, the Vite `external`/`globals` config, and the *never bundle your own React* rule. `@proveanything/smartlinks-auth-ui` is externalized too (host global `window.SmartlinksAuthUI`) — do not ship a second copy.
|
|
602
544
|
|
|
603
545
|
### Enabling Widget Builds
|
|
604
546
|
|
|
@@ -626,7 +568,7 @@ import * as SL from '@proveanything/smartlinks';
|
|
|
626
568
|
|
|
627
569
|
// Dynamic import from app's CDN
|
|
628
570
|
const CompetitionWidget = lazy(() =>
|
|
629
|
-
import('https://competition-app.example.com/widgets.
|
|
571
|
+
import('https://competition-app.example.com/widgets.esm.js')
|
|
630
572
|
.then(m => ({ default: m.CompetitionWidget }))
|
|
631
573
|
);
|
|
632
574
|
|
|
@@ -670,7 +612,7 @@ import { WidgetWrapper, CompetitionWidget } from 'competition-app/widgets';
|
|
|
670
612
|
import { WIDGET_MANIFEST } from 'competition-app/widgets';
|
|
671
613
|
|
|
672
614
|
// Verify React version compatibility
|
|
673
|
-
if (!WIDGET_MANIFEST.reactVersion.startsWith('
|
|
615
|
+
if (!WIDGET_MANIFEST.reactVersion.startsWith('19')) {
|
|
674
616
|
console.warn('Widget React version mismatch');
|
|
675
617
|
}
|
|
676
618
|
|
|
@@ -741,7 +683,7 @@ Each app exports a `WIDGET_MANIFEST` for discovery:
|
|
|
741
683
|
```typescript
|
|
742
684
|
export const WIDGET_MANIFEST = {
|
|
743
685
|
version: '1.0.0', // Widget bundle version
|
|
744
|
-
reactVersion: '
|
|
686
|
+
reactVersion: '19.x', // Required React version
|
|
745
687
|
widgets: [
|
|
746
688
|
{
|
|
747
689
|
name: 'ExampleWidget',
|
package/openapi.yaml
CHANGED
|
@@ -17471,6 +17471,18 @@ components:
|
|
|
17471
17471
|
type: string
|
|
17472
17472
|
appId:
|
|
17473
17473
|
type: string
|
|
17474
|
+
moduleFormat:
|
|
17475
|
+
type: string
|
|
17476
|
+
enum:
|
|
17477
|
+
- umd
|
|
17478
|
+
- esm
|
|
17479
|
+
- dual
|
|
17480
|
+
sharedDependencies:
|
|
17481
|
+
type: string
|
|
17482
|
+
globals:
|
|
17483
|
+
type: object
|
|
17484
|
+
additionalProperties:
|
|
17485
|
+
type: string
|
|
17474
17486
|
seo:
|
|
17475
17487
|
type: object
|
|
17476
17488
|
additionalProperties: true
|
package/package.json
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proveanything/smartlinks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
9
10
|
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
10
12
|
"default": "./dist/index.js"
|
|
11
13
|
},
|
|
12
14
|
"./testing": {
|
|
13
15
|
"types": "./dist/testing/index.d.ts",
|
|
16
|
+
"import": "./dist/testing/index.js",
|
|
14
17
|
"default": "./dist/testing/index.js"
|
|
15
|
-
}
|
|
18
|
+
},
|
|
19
|
+
"./*": {
|
|
20
|
+
"types": "./dist/*.d.ts",
|
|
21
|
+
"import": "./dist/*.js",
|
|
22
|
+
"default": "./dist/*.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
16
25
|
},
|
|
17
26
|
"bin": {
|
|
18
27
|
"smartlinks-register-release": "scripts/register-release.mjs",
|
|
19
|
-
"smartlinks-publish": "scripts/publish.mjs"
|
|
28
|
+
"smartlinks-publish": "scripts/publish.mjs",
|
|
29
|
+
"smartlinks-doctor": "scripts/doctor.mjs"
|
|
20
30
|
},
|
|
21
31
|
"files": [
|
|
22
32
|
"dist/",
|
|
23
33
|
"docs/",
|
|
24
34
|
"scripts/register-release.mjs",
|
|
25
35
|
"scripts/publish.mjs",
|
|
36
|
+
"scripts/doctor.mjs",
|
|
26
37
|
"openapi.yaml",
|
|
27
38
|
"README.md"
|
|
28
39
|
],
|
|
29
40
|
"scripts": {
|
|
30
41
|
"test": "node test/run.cjs",
|
|
31
|
-
"build": "tsc && node generate-api-summary.
|
|
42
|
+
"build": "tsc && node scripts/fix-esm-extensions.mjs && node generate-api-summary.cjs && node generate-openapi.cjs && node scripts/copy-docs-to-dist.cjs",
|
|
32
43
|
"docs": "typedoc",
|
|
33
|
-
"docs:summary": "node generate-api-summary.
|
|
34
|
-
"docs:openapi": "node generate-openapi.
|
|
44
|
+
"docs:summary": "node generate-api-summary.cjs",
|
|
45
|
+
"docs:openapi": "node generate-openapi.cjs",
|
|
35
46
|
"build:docs": "tsc build-docs.ts --outDir dist && node dist/build-docs.js",
|
|
36
47
|
"prepublishOnly": "npm run build"
|
|
37
48
|
},
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// smartlinks doctor — verify an app's ESM bundles against the shared-dependency
|
|
4
|
+
// contract, so a modern (esm/dual) app can't ship a bare import the host won't
|
|
5
|
+
// resolve.
|
|
6
|
+
//
|
|
7
|
+
// A correctly-built externalized ESM bundle inlines everything EXCEPT the host's
|
|
8
|
+
// shared singletons. So every *bare* import left in the output must be a contract
|
|
9
|
+
// entry — anything else will either fail to resolve through the import map at
|
|
10
|
+
// runtime, or silently pull in a second copy (the duplicate-React class of bug).
|
|
11
|
+
//
|
|
12
|
+
// Usage:
|
|
13
|
+
// smartlinks-doctor [appDir] # defaults to cwd
|
|
14
|
+
// npx @proveanything/smartlinks doctor (once a unified `smartlinks` bin exists)
|
|
15
|
+
//
|
|
16
|
+
// Exit code: 0 = clean, 1 = violations (CI-friendly).
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
20
|
+
import { resolve, dirname, join } from 'node:path';
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
// Read the contract from THIS SDK build — one source of truth with the runtime.
|
|
26
|
+
const contractUrl = pathToFileURL(resolve(here, '../dist/shared-dependencies.js')).href;
|
|
27
|
+
const { SHARED_DEPENDENCY_SPECIFIERS, SHARED_DEPENDENCY_CONTRACT_VERSION } = await import(contractUrl);
|
|
28
|
+
const CONTRACT = new Set(SHARED_DEPENDENCY_SPECIFIERS);
|
|
29
|
+
|
|
30
|
+
const RED = '\x1b[31m';
|
|
31
|
+
const GREEN = '\x1b[32m';
|
|
32
|
+
const YELLOW = '\x1b[33m';
|
|
33
|
+
const DIM = '\x1b[2m';
|
|
34
|
+
const BOLD = '\x1b[1m';
|
|
35
|
+
const RESET = '\x1b[0m';
|
|
36
|
+
|
|
37
|
+
function die(msg) {
|
|
38
|
+
console.error(`${RED}smartlinks doctor: ${msg}${RESET}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const appDir = resolve(process.argv[2] || process.cwd());
|
|
43
|
+
if (!existsSync(appDir)) die(`app directory not found: ${appDir}`);
|
|
44
|
+
|
|
45
|
+
// Locate the manifest.
|
|
46
|
+
const manifestPath = ['public/app.manifest.json', 'app.manifest.json', 'dist/app.manifest.json']
|
|
47
|
+
.map((p) => join(appDir, p))
|
|
48
|
+
.find(existsSync);
|
|
49
|
+
if (!manifestPath) die(`no app.manifest.json found under ${appDir} (looked in public/, ., dist/)`);
|
|
50
|
+
|
|
51
|
+
let manifest;
|
|
52
|
+
try {
|
|
53
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
54
|
+
} catch (e) {
|
|
55
|
+
die(`could not parse ${manifestPath}: ${e.message}`);
|
|
56
|
+
}
|
|
57
|
+
const manifestDir = dirname(manifestPath);
|
|
58
|
+
const meta = manifest.meta || {};
|
|
59
|
+
|
|
60
|
+
console.log(`${BOLD}SmartLinks doctor${RESET} ${DIM}— ${appDir}${RESET}`);
|
|
61
|
+
console.log(`${DIM}manifest:${RESET} ${manifestPath.replace(appDir + '\\', '').replace(appDir + '/', '')}`);
|
|
62
|
+
console.log(`${DIM}contract:${RESET} ${SHARED_DEPENDENCY_CONTRACT_VERSION} (${CONTRACT.size} shared deps)`);
|
|
63
|
+
console.log(`${DIM}moduleFormat:${RESET} ${meta.moduleFormat || '(absent → umd)'} · ${DIM}sharedDependencies:${RESET} ${meta.sharedDependencies || '(none)'}\n`);
|
|
64
|
+
|
|
65
|
+
const problems = [];
|
|
66
|
+
const warnings = [];
|
|
67
|
+
|
|
68
|
+
// Manifest-level sanity.
|
|
69
|
+
const format = meta.moduleFormat || 'umd';
|
|
70
|
+
const checkEsm = format === 'esm' || format === 'dual';
|
|
71
|
+
if (checkEsm && !meta.sharedDependencies) {
|
|
72
|
+
warnings.push(`meta.moduleFormat is "${format}" but meta.sharedDependencies is not declared — set it to "${SHARED_DEPENDENCY_CONTRACT_VERSION}".`);
|
|
73
|
+
}
|
|
74
|
+
if (meta.sharedDependencies && meta.sharedDependencies !== SHARED_DEPENDENCY_CONTRACT_VERSION) {
|
|
75
|
+
warnings.push(`built against contract ${meta.sharedDependencies}, but this SDK ships ${SHARED_DEPENDENCY_CONTRACT_VERSION} — host may not serve a matching import map.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Pull the bare imports out of a bundle (handles minified `from"x"` too).
|
|
79
|
+
function bareImportsOf(code) {
|
|
80
|
+
const specs = new Set();
|
|
81
|
+
const patterns = [
|
|
82
|
+
/\bfrom\s*["']([^"']+)["']/g, // static import/export ... from "x"
|
|
83
|
+
/\bimport\s*["']([^"']+)["']/g, // side-effect import "x"
|
|
84
|
+
/\bimport\(\s*["']([^"']+)["']\s*\)/g, // dynamic import("x")
|
|
85
|
+
];
|
|
86
|
+
for (const re of patterns) {
|
|
87
|
+
let m;
|
|
88
|
+
while ((m = re.exec(code))) {
|
|
89
|
+
const spec = m[1];
|
|
90
|
+
if (spec.startsWith('.') || spec.startsWith('/')) continue; // internal/relative
|
|
91
|
+
if (/^https?:/.test(spec)) continue; // absolute URL import
|
|
92
|
+
specs.add(spec);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [...specs];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Resolve a manifest-relative bundle path against the likely roots (dist paths
|
|
99
|
+
// are sometimes relative to the app root, sometimes to the manifest dir).
|
|
100
|
+
function resolveBundle(rel) {
|
|
101
|
+
const bases = [manifestDir, appDir, join(appDir, 'public'), join(appDir, 'dist')];
|
|
102
|
+
for (const b of bases) {
|
|
103
|
+
const p = resolve(b, rel);
|
|
104
|
+
if (existsSync(p)) return p;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check each ESM surface declared in the manifest.
|
|
110
|
+
const surfaces = [
|
|
111
|
+
['widgets', manifest.widgets],
|
|
112
|
+
['containers', manifest.containers],
|
|
113
|
+
['mobileAdmin', manifest.mobileAdmin],
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
let checkedAny = false;
|
|
117
|
+
for (const [name, block] of surfaces) {
|
|
118
|
+
const esm = block?.files?.js?.esm;
|
|
119
|
+
if (!esm) continue;
|
|
120
|
+
|
|
121
|
+
// UMD-format app that still declares an ESM bundle: the host won't load it, so
|
|
122
|
+
// it's dead weight and a common scaffold-confusion source — warn, don't fail.
|
|
123
|
+
if (!checkEsm) {
|
|
124
|
+
warnings.push(`${name}: ESM bundle "${esm}" is declared but meta.moduleFormat is "${format}", so the host never loads it. Set moduleFormat to "dual" to use it, or drop the esm entry.`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
checkedAny = true;
|
|
129
|
+
const bundlePath = resolveBundle(esm);
|
|
130
|
+
if (!bundlePath) {
|
|
131
|
+
console.log(`${RED}✗${RESET} ${name} ${DIM}(${esm})${RESET} — declared ESM bundle not found on disk`);
|
|
132
|
+
problems.push({ surface: name, spec: null, msg: `declared ESM bundle not found: ${esm}` });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const code = readFileSync(bundlePath, 'utf8');
|
|
136
|
+
const imports = bareImportsOf(code);
|
|
137
|
+
const offenders = imports.filter((s) => !CONTRACT.has(s));
|
|
138
|
+
const ok = imports.length - offenders.length;
|
|
139
|
+
|
|
140
|
+
if (offenders.length === 0) {
|
|
141
|
+
console.log(`${GREEN}✓${RESET} ${name} ${DIM}(${esm})${RESET} — ${ok} bare import${ok === 1 ? '' : 's'}, all in contract`);
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`${RED}✗${RESET} ${name} ${DIM}(${esm})${RESET} — ${offenders.length} outside the contract:`);
|
|
144
|
+
for (const spec of offenders) {
|
|
145
|
+
const hint = spec.startsWith('node:')
|
|
146
|
+
? 'node built-in — must not appear in a browser bundle'
|
|
147
|
+
: 'not host-provided — bundle it (don\'t externalize) or it will fail to resolve / double-load';
|
|
148
|
+
console.log(` ${RED}${spec}${RESET} ${DIM}— ${hint}${RESET}`);
|
|
149
|
+
problems.push({ surface: name, spec, msg: hint });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (checkEsm && !checkedAny) {
|
|
155
|
+
warnings.push(`moduleFormat is "${format}" but no surface declares files.js.esm — nothing to check.`);
|
|
156
|
+
}
|
|
157
|
+
if (!checkEsm && problems.length === 0) {
|
|
158
|
+
console.log(`${DIM}moduleFormat "${format}" — ESM path not in use; UMD bundles resolve shared deps from window globals.${RESET}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('');
|
|
162
|
+
for (const w of warnings) console.log(`${YELLOW}⚠ ${w}${RESET}`);
|
|
163
|
+
|
|
164
|
+
if (problems.length) {
|
|
165
|
+
console.log(`\n${RED}${BOLD}FAIL${RESET} — ${problems.length} problem${problems.length === 1 ? '' : 's'} outside the shared-dependency contract.`);
|
|
166
|
+
console.log(`${DIM}Externalize only the ${CONTRACT.size} contract specifiers (import { SHARED_DEPENDENCY_SPECIFIERS } from '@proveanything/smartlinks'); bundle everything else.${RESET}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(`${GREEN}${BOLD}OK${RESET} — bundles conform to shared-dependency contract ${SHARED_DEPENDENCY_CONTRACT_VERSION}.`);
|
|
171
|
+
process.exit(0);
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Analytics Metadata Conventions
|
|
2
|
-
|
|
3
|
-
Use these as the recommended standard analytics keys.
|
|
4
|
-
|
|
5
|
-
Some of these are now promoted top-level analytics fields. Others remain good metadata keys for custom dimensions.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Recommended Keys
|
|
10
|
-
|
|
11
|
-
### Promoted top-level fields
|
|
12
|
-
|
|
13
|
-
- `visitorId`
|
|
14
|
-
- `referrerHost`
|
|
15
|
-
- `entryType`
|
|
16
|
-
- `pageId`
|
|
17
|
-
- `scanMethod`
|
|
18
|
-
- `source` - collection/web-events only. Free-form client app identifier, e.g. `'portal'`, `'hub'`. No enum/whitelist. Not available on tag events - see the `analytics.tag.track(event)` section in [docs/analytics.md](analytics.md) for why.
|
|
19
|
-
- `redirectMode` - tag-events only. Usually server-written; only set this yourself if you're logging a redirect-style event.
|
|
20
|
-
|
|
21
|
-
These should be sent as top-level analytics fields, not inside `metadata`.
|
|
22
|
-
|
|
23
|
-
### Metadata-friendly keys
|
|
24
|
-
|
|
25
|
-
- `referrer`
|
|
26
|
-
- `utmSource`
|
|
27
|
-
- `utmMedium`
|
|
28
|
-
- `utmCampaign`
|
|
29
|
-
- `utmContent`
|
|
30
|
-
- `utmTerm`
|
|
31
|
-
- `group`
|
|
32
|
-
- `tag`
|
|
33
|
-
- `campaign`
|
|
34
|
-
- `placement`
|
|
35
|
-
- `linkGroup`
|
|
36
|
-
- `linkPlacement`
|
|
37
|
-
- `linkPosition`
|
|
38
|
-
- `linkTitle`
|
|
39
|
-
- `destinationDomain`
|
|
40
|
-
- `pagePath`
|
|
41
|
-
- `qrCodeId`
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## Why These Matter
|
|
46
|
-
|
|
47
|
-
These keys give teams a shared vocabulary for:
|
|
48
|
-
|
|
49
|
-
- inbound traffic attribution
|
|
50
|
-
- outbound link analysis
|
|
51
|
-
- link placement and link-tree performance
|
|
52
|
-
- QR and page-level traffic grouping
|
|
53
|
-
- physical scan source analysis
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## Recommendation
|
|
58
|
-
|
|
59
|
-
- Treat these as reserved standard keys.
|
|
60
|
-
- Prefer these names before inventing custom alternatives.
|
|
61
|
-
- Send promoted fields at top level.
|
|
62
|
-
- Keep values flat and scalar where possible so they are easier to filter and break down later.
|
|
63
|
-
- Promote a field to a first-class backend column only when it becomes a hot platform-wide dimension.
|
|
64
|
-
- Note: `source` (the event column) and the query-time `source` parameter (`'events' | 'tag'`, which table to query) are unrelated fields that happen to share a name. When filtering by the `source` column, use the plural `sources` array - there is no singular `source` filter, precisely to avoid colliding with the table selector.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Example
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
analytics.collection.track({
|
|
72
|
-
sessionId: 1234567890,
|
|
73
|
-
eventType: 'click_link',
|
|
74
|
-
collectionId: 'demo-collection',
|
|
75
|
-
visitorId: 'visitor_123',
|
|
76
|
-
linkId: 'hero-cta',
|
|
77
|
-
href: 'https://example.com/buy',
|
|
78
|
-
referrerHost: 'instagram.com',
|
|
79
|
-
placement: 'hero',
|
|
80
|
-
campaign: 'summer-launch',
|
|
81
|
-
utmSource: 'email',
|
|
82
|
-
pageId: 'QR123',
|
|
83
|
-
source: 'portal',
|
|
84
|
-
metadata: {
|
|
85
|
-
pagePath: '/c/demo-collection',
|
|
86
|
-
},
|
|
87
|
-
})
|
|
88
|
-
```
|