@embeddedcanvas/embed-sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/jwt.d.ts +20 -0
- package/dist/jwt.js +38 -0
- package/dist/mount.d.ts +27 -0
- package/dist/mount.js +90 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.js +3 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +48 -0
- package/dist/session.d.ts +35 -0
- package/dist/session.js +43 -0
- package/dist/ui.d.ts +12 -0
- package/dist/ui.js +11 -0
- package/dist/urls.d.ts +8 -0
- package/dist/urls.js +26 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Embedded Canvas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @embeddedcanvas/embed-sdk
|
|
2
|
+
|
|
3
|
+
Embedded Canvas integrator SDK — mint workspace JWTs server-side, fetch embed sessions, mount white-label analytics dashboards.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @embeddedcanvas/embed-sdk @superset-ui/embedded-sdk
|
|
9
|
+
# React helper (optional):
|
|
10
|
+
npm install react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Monorepo: `"@embeddedcanvas/embed-sdk": "workspace:*"`.
|
|
14
|
+
|
|
15
|
+
### Migrating from `@kepler/embed-sdk`
|
|
16
|
+
|
|
17
|
+
The package was renamed in v1.0.0 with **identical exports**. Update imports only:
|
|
18
|
+
|
|
19
|
+
```diff
|
|
20
|
+
- import { mintEmbedJwt } from "@kepler/embed-sdk/jwt";
|
|
21
|
+
+ import { mintEmbedJwt } from "@embeddedcanvas/embed-sdk/jwt";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Path A — Analytics dashboard (recommended)
|
|
27
|
+
|
|
28
|
+
### 1. Server: mint workspace JWT
|
|
29
|
+
|
|
30
|
+
Store the signing key from `POST /workspaces/me/signing-key` in your backend env (`EMBEDDED_CANVAS_SIGNING_KEY`).
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { mintEmbedJwt } from "@embeddedcanvas/embed-sdk/jwt";
|
|
34
|
+
|
|
35
|
+
const jwt = mintEmbedJwt({
|
|
36
|
+
sub: DASHBOARD_ROW_ID, // control-plane id, not Superset uuid
|
|
37
|
+
signingKey: process.env.EMBEDDED_CANVAS_SIGNING_KEY!,
|
|
38
|
+
ttlSeconds: 900,
|
|
39
|
+
customerId: user.customerId,
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Pass `jwt` to the browser (HttpOnly cookie, BFF, or short-lived inline bootstrap — never expose the signing key).
|
|
44
|
+
|
|
45
|
+
### 2. Browser: React component
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { EmbeddedAnalytics } from "@embeddedcanvas/embed-sdk/react";
|
|
49
|
+
|
|
50
|
+
<EmbeddedAnalytics
|
|
51
|
+
apiBaseUrl="https://api.example.com"
|
|
52
|
+
embedToken="EMBED_TOKEN_FROM_ANALYTICS_PAGE"
|
|
53
|
+
customerId={user.customerId}
|
|
54
|
+
jwt={jwtFromYourBackend}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Browser: vanilla / custom framework
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { mountAnalyticsEmbed } from "@embeddedcanvas/embed-sdk";
|
|
62
|
+
|
|
63
|
+
await mountAnalyticsEmbed({
|
|
64
|
+
apiBaseUrl: "https://api.example.com",
|
|
65
|
+
embedToken: "…",
|
|
66
|
+
customerId: "1000",
|
|
67
|
+
jwt,
|
|
68
|
+
mountPoint: document.getElementById("analytics")!,
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Handles session API → guest token refresh → `@superset-ui/embedded-sdk` with white-label UI defaults.
|
|
73
|
+
|
|
74
|
+
### 4. Hosted iframe (no SDK)
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { analyticsHostedEmbedUrl } from "@embeddedcanvas/embed-sdk";
|
|
78
|
+
|
|
79
|
+
const src = analyticsHostedEmbedUrl("https://app.example.com", embedToken, {
|
|
80
|
+
customerId: "1000",
|
|
81
|
+
jwt,
|
|
82
|
+
});
|
|
83
|
+
// <iframe src={src} />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use your workspace **custom embed domain** as `appBaseUrl` when configured.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Session API (direct)
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import {
|
|
94
|
+
fetchAnalyticsEmbedSession,
|
|
95
|
+
createEmbedSessionFetcher,
|
|
96
|
+
EmbedSessionError,
|
|
97
|
+
} from "@embeddedcanvas/embed-sdk";
|
|
98
|
+
|
|
99
|
+
const session = await fetchAnalyticsEmbedSession({
|
|
100
|
+
apiBaseUrl: "https://api.example.com",
|
|
101
|
+
embedToken: "…",
|
|
102
|
+
customerId: "1000",
|
|
103
|
+
jwt,
|
|
104
|
+
});
|
|
105
|
+
// session.guestToken, session.supersetDomain, session.embedUiConfig, …
|
|
106
|
+
|
|
107
|
+
const fetchGuestToken = createEmbedSessionFetcher({
|
|
108
|
+
apiBaseUrl: "https://api.example.com",
|
|
109
|
+
embedToken: "…",
|
|
110
|
+
customerId: "1000",
|
|
111
|
+
getJwt: () => jwt,
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`EmbedSessionError` includes `status` and optional `code` (e.g. `embed_view_limit`).
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Embed API keys (no JWT on your backend)
|
|
120
|
+
|
|
121
|
+
If you use `ec_live_…` keys from the control plane, call `POST /public/embed/v1/sessions` from your server instead of `mintEmbedJwt`. See your operator docs for `EMBED_API_KEYS`.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Legacy chart embeds
|
|
126
|
+
|
|
127
|
+
Chart embeds use the same JWT helper with a chart id as `sub`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const jwt = mintEmbedJwt({
|
|
131
|
+
sub: chartId,
|
|
132
|
+
signingKey: process.env.EMBEDDED_CANVAS_SIGNING_KEY!,
|
|
133
|
+
ttlSeconds: 900,
|
|
134
|
+
claims: { tenant_id: user.tenantId },
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Iframe: `https://app.example.com/embed/{embedToken}?jwt=…`
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Security
|
|
143
|
+
|
|
144
|
+
- Signing key: **backend only**, never in frontend bundles.
|
|
145
|
+
- JWT `sub` must match the dashboard/chart row id in the control plane.
|
|
146
|
+
- `customer_id` in query must match JWT when both are present (RLS binding).
|
|
147
|
+
- Production requires JWT on session API (`isPublic` is dev-only).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Related docs
|
|
152
|
+
|
|
153
|
+
- [docs/embed/SDK.md](../../docs/embed/SDK.md) — full integration guide
|
|
154
|
+
- [docs/embed/NPM_SDK.md](../../docs/embed/NPM_SDK.md) — publish / release
|
|
155
|
+
- [docs/embed/EMBED_GATEWAY.md](../../docs/embed/EMBED_GATEWAY.md)
|
|
156
|
+
- [docs/embed/WHITE_LABEL_EMBED.md](../../docs/embed/WHITE_LABEL_EMBED.md)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe Embedded Canvas SDK (session API, mount, URLs).
|
|
3
|
+
* Server-side JWT minting: import from `@embeddedcanvas/embed-sdk/jwt`.
|
|
4
|
+
*/
|
|
5
|
+
export { fetchAnalyticsEmbedSession, createEmbedSessionFetcher, EmbedSessionError, type AnalyticsEmbedSession, type EmbedSessionFetcherOptions, } from "./session.js";
|
|
6
|
+
export { analyticsSessionApiPath, analyticsSessionApiUrl, analyticsHostedEmbedPath, analyticsHostedEmbedUrl, type EmbedUrlParams, } from "./urls.js";
|
|
7
|
+
export { DEFAULT_EMBEDDED_DASHBOARD_UI_CONFIG, type EmbeddedDashboardUiConfig, } from "./ui.js";
|
|
8
|
+
export { mountAnalyticsEmbed, mountEmbeddedAnalyticsDashboard, type MountAnalyticsEmbedInput, type AnalyticsEmbedController, type EmbedDashboardArgs, } from "./mount.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe Embedded Canvas SDK (session API, mount, URLs).
|
|
3
|
+
* Server-side JWT minting: import from `@embeddedcanvas/embed-sdk/jwt`.
|
|
4
|
+
*/
|
|
5
|
+
export { fetchAnalyticsEmbedSession, createEmbedSessionFetcher, EmbedSessionError, } from "./session.js";
|
|
6
|
+
export { analyticsSessionApiPath, analyticsSessionApiUrl, analyticsHostedEmbedPath, analyticsHostedEmbedUrl, } from "./urls.js";
|
|
7
|
+
export { DEFAULT_EMBEDDED_DASHBOARD_UI_CONFIG, } from "./ui.js";
|
|
8
|
+
export { mountAnalyticsEmbed, mountEmbeddedAnalyticsDashboard, } from "./mount.js";
|
package/dist/jwt.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type MintEmbedJwtInput = {
|
|
2
|
+
/** Control-plane resource id — dashboard row id (Path A) or chart id (legacy). JWT `sub`. */
|
|
3
|
+
sub: string;
|
|
4
|
+
/** Workspace signing key (hex) from POST /workspaces/me/signing-key — server-side only. */
|
|
5
|
+
signingKey: string;
|
|
6
|
+
ttlSeconds: number;
|
|
7
|
+
/** End-customer tenant key — bound into RLS as `customer_id` when present. */
|
|
8
|
+
customerId?: string;
|
|
9
|
+
/** Extra claims under the JWT `jwt` object (e.g. role, user_id). */
|
|
10
|
+
claims?: Record<string, string | number | boolean | null>;
|
|
11
|
+
};
|
|
12
|
+
/** @deprecated Use `MintEmbedJwtInput` with `sub`. */
|
|
13
|
+
export type MintInput = {
|
|
14
|
+
chartId: string;
|
|
15
|
+
signingKey: string;
|
|
16
|
+
ttlSeconds: number;
|
|
17
|
+
claims?: Record<string, string | number | boolean | null>;
|
|
18
|
+
};
|
|
19
|
+
export declare function mintEmbedJwt(input: MintEmbedJwtInput): string;
|
|
20
|
+
export declare function mintEmbedJwt(input: MintInput): string;
|
package/dist/jwt.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createHmac } from "crypto";
|
|
2
|
+
const HEADER_B64 = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
3
|
+
export function mintEmbedJwt(input) {
|
|
4
|
+
const sub = "sub" in input ? input.sub : input.chartId;
|
|
5
|
+
const jwtClaims = {
|
|
6
|
+
...(input.claims ?? {}),
|
|
7
|
+
};
|
|
8
|
+
if ("customerId" in input && input.customerId) {
|
|
9
|
+
jwtClaims.customer_id = input.customerId;
|
|
10
|
+
}
|
|
11
|
+
const now = Math.floor(Date.now() / 1000);
|
|
12
|
+
const payload = {
|
|
13
|
+
sub,
|
|
14
|
+
jwt: jwtClaims,
|
|
15
|
+
iat: now,
|
|
16
|
+
exp: now + input.ttlSeconds,
|
|
17
|
+
};
|
|
18
|
+
const payloadB64 = b64url(JSON.stringify(payload));
|
|
19
|
+
const signingInput = `${HEADER_B64}.${payloadB64}`;
|
|
20
|
+
const sigBytes = createHmac("sha256", Buffer.from(input.signingKey, "hex"))
|
|
21
|
+
.update(signingInput)
|
|
22
|
+
.digest();
|
|
23
|
+
return `${signingInput}.${b64urlBytes(sigBytes)}`;
|
|
24
|
+
}
|
|
25
|
+
function b64url(s) {
|
|
26
|
+
return Buffer.from(s, "utf8")
|
|
27
|
+
.toString("base64")
|
|
28
|
+
.replace(/\+/g, "-")
|
|
29
|
+
.replace(/\//g, "_")
|
|
30
|
+
.replace(/=+$/, "");
|
|
31
|
+
}
|
|
32
|
+
function b64urlBytes(buf) {
|
|
33
|
+
return buf
|
|
34
|
+
.toString("base64")
|
|
35
|
+
.replace(/\+/g, "-")
|
|
36
|
+
.replace(/\//g, "_")
|
|
37
|
+
.replace(/=+$/, "");
|
|
38
|
+
}
|
package/dist/mount.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type EmbeddedDashboardUiConfig } from "./ui.js";
|
|
2
|
+
export type MountAnalyticsEmbedInput = {
|
|
3
|
+
apiBaseUrl: string;
|
|
4
|
+
embedToken: string;
|
|
5
|
+
customerId: string;
|
|
6
|
+
mountPoint: HTMLElement;
|
|
7
|
+
jwt?: string;
|
|
8
|
+
getJwt?: () => Promise<string | undefined> | string | undefined;
|
|
9
|
+
uiConfig?: EmbeddedDashboardUiConfig;
|
|
10
|
+
};
|
|
11
|
+
export type AnalyticsEmbedController = {
|
|
12
|
+
destroy: () => void;
|
|
13
|
+
refreshGuestToken: () => Promise<string>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Fetch session + mount white-label analytics dashboard (requires `@superset-ui/embedded-sdk`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function mountAnalyticsEmbed(input: MountAnalyticsEmbedInput): Promise<AnalyticsEmbedController>;
|
|
19
|
+
export type EmbedDashboardArgs = {
|
|
20
|
+
embeddedDashboardId: string;
|
|
21
|
+
supersetDomain: string;
|
|
22
|
+
mountPoint: HTMLElement;
|
|
23
|
+
fetchGuestToken: () => Promise<string>;
|
|
24
|
+
uiConfig?: EmbeddedDashboardUiConfig;
|
|
25
|
+
};
|
|
26
|
+
/** Low-level mount when you already have session fields. */
|
|
27
|
+
export declare function mountEmbeddedAnalyticsDashboard(args: EmbedDashboardArgs): Promise<void>;
|
package/dist/mount.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createEmbedSessionFetcher, fetchAnalyticsEmbedSession } from "./session.js";
|
|
2
|
+
import { DEFAULT_EMBEDDED_DASHBOARD_UI_CONFIG, } from "./ui.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fetch session + mount white-label analytics dashboard (requires `@superset-ui/embedded-sdk`).
|
|
5
|
+
*/
|
|
6
|
+
export async function mountAnalyticsEmbed(input) {
|
|
7
|
+
const jwt = input.getJwt ? await input.getJwt() : input.jwt;
|
|
8
|
+
const session = await fetchAnalyticsEmbedSession({
|
|
9
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
10
|
+
embedToken: input.embedToken,
|
|
11
|
+
customerId: input.customerId,
|
|
12
|
+
jwt,
|
|
13
|
+
});
|
|
14
|
+
const fetchGuestToken = createEmbedSessionFetcher({
|
|
15
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
16
|
+
embedToken: input.embedToken,
|
|
17
|
+
customerId: input.customerId,
|
|
18
|
+
getJwt: input.getJwt ?? (input.jwt ? () => input.jwt : undefined),
|
|
19
|
+
});
|
|
20
|
+
await mountEmbeddedAnalyticsDashboard({
|
|
21
|
+
embeddedDashboardId: session.embeddedDashboardId,
|
|
22
|
+
supersetDomain: session.supersetDomain,
|
|
23
|
+
mountPoint: input.mountPoint,
|
|
24
|
+
fetchGuestToken,
|
|
25
|
+
uiConfig: input.uiConfig ?? session.embedUiConfig,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
destroy: () => {
|
|
29
|
+
input.mountPoint.innerHTML = "";
|
|
30
|
+
},
|
|
31
|
+
refreshGuestToken: fetchGuestToken,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** @superset-ui/embedded-sdk often leaves the iframe at 300×150 unless sized explicitly. */
|
|
35
|
+
function sizeEmbeddedIframe(mountPoint) {
|
|
36
|
+
const apply = () => {
|
|
37
|
+
const iframe = mountPoint.querySelector("iframe");
|
|
38
|
+
if (!iframe)
|
|
39
|
+
return false;
|
|
40
|
+
const { width, height } = mountPoint.getBoundingClientRect();
|
|
41
|
+
iframe.style.border = "0";
|
|
42
|
+
iframe.style.display = "block";
|
|
43
|
+
iframe.removeAttribute("width");
|
|
44
|
+
iframe.removeAttribute("height");
|
|
45
|
+
if (width > 0 && height > 0) {
|
|
46
|
+
iframe.style.width = `${Math.floor(width)}px`;
|
|
47
|
+
iframe.style.height = `${Math.floor(height)}px`;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
iframe.style.width = "100%";
|
|
51
|
+
iframe.style.height = "100%";
|
|
52
|
+
iframe.style.minHeight = "100%";
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
const bindResize = () => {
|
|
57
|
+
apply();
|
|
58
|
+
const ro = new ResizeObserver(() => apply());
|
|
59
|
+
ro.observe(mountPoint);
|
|
60
|
+
mountPoint.dataset.ecEmbedResize = "1";
|
|
61
|
+
};
|
|
62
|
+
if (apply()) {
|
|
63
|
+
bindResize();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const observer = new MutationObserver(() => {
|
|
67
|
+
if (apply()) {
|
|
68
|
+
observer.disconnect();
|
|
69
|
+
bindResize();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
observer.observe(mountPoint, { childList: true, subtree: true });
|
|
73
|
+
window.setTimeout(() => observer.disconnect(), 15_000);
|
|
74
|
+
}
|
|
75
|
+
/** Low-level mount when you already have session fields. */
|
|
76
|
+
export async function mountEmbeddedAnalyticsDashboard(args) {
|
|
77
|
+
const { embedDashboard } = await import("@superset-ui/embedded-sdk");
|
|
78
|
+
const ui = args.uiConfig ?? DEFAULT_EMBEDDED_DASHBOARD_UI_CONFIG;
|
|
79
|
+
const mountPoint = args.mountPoint;
|
|
80
|
+
mountPoint.style.width = mountPoint.style.width || "100%";
|
|
81
|
+
mountPoint.style.height = mountPoint.style.height || "100%";
|
|
82
|
+
await embedDashboard({
|
|
83
|
+
id: args.embeddedDashboardId,
|
|
84
|
+
supersetDomain: args.supersetDomain.replace(/\/$/, ""),
|
|
85
|
+
mountPoint,
|
|
86
|
+
fetchGuestToken: args.fetchGuestToken,
|
|
87
|
+
dashboardUiConfig: ui,
|
|
88
|
+
});
|
|
89
|
+
sizeEmbeddedIframe(mountPoint);
|
|
90
|
+
}
|
package/dist/node.d.ts
ADDED
package/dist/node.js
ADDED
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EmbeddedDashboardUiConfig } from "./ui.js";
|
|
2
|
+
export type EmbeddedAnalyticsProps = {
|
|
3
|
+
apiBaseUrl: string;
|
|
4
|
+
embedToken: string;
|
|
5
|
+
customerId: string;
|
|
6
|
+
jwt?: string;
|
|
7
|
+
getJwt?: () => Promise<string | undefined> | string | undefined;
|
|
8
|
+
uiConfig?: EmbeddedDashboardUiConfig;
|
|
9
|
+
className?: string;
|
|
10
|
+
onError?: (error: Error) => void;
|
|
11
|
+
onReady?: () => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* React shell for Path A analytics embeds.
|
|
15
|
+
* Requires `react`, `@superset-ui/embedded-sdk` (pulled in by mount helper).
|
|
16
|
+
*/
|
|
17
|
+
export declare function EmbeddedAnalytics({ apiBaseUrl, embedToken, customerId, jwt, getJwt, uiConfig, className, onError, onReady, }: EmbeddedAnalyticsProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { mountAnalyticsEmbed } from "./mount.js";
|
|
5
|
+
/**
|
|
6
|
+
* React shell for Path A analytics embeds.
|
|
7
|
+
* Requires `react`, `@superset-ui/embedded-sdk` (pulled in by mount helper).
|
|
8
|
+
*/
|
|
9
|
+
export function EmbeddedAnalytics({ apiBaseUrl, embedToken, customerId, jwt, getJwt, uiConfig, className = "h-[480px] w-full min-h-[320px] [&_iframe]:h-full [&_iframe]:w-full [&_iframe]:border-0", onError, onReady, }) {
|
|
10
|
+
const mountRef = useRef(null);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const el = mountRef.current;
|
|
14
|
+
if (!el || !embedToken || !customerId)
|
|
15
|
+
return;
|
|
16
|
+
let cancelled = false;
|
|
17
|
+
el.innerHTML = "";
|
|
18
|
+
setError(null);
|
|
19
|
+
void mountAnalyticsEmbed({
|
|
20
|
+
apiBaseUrl,
|
|
21
|
+
embedToken,
|
|
22
|
+
customerId,
|
|
23
|
+
mountPoint: el,
|
|
24
|
+
jwt,
|
|
25
|
+
getJwt,
|
|
26
|
+
uiConfig,
|
|
27
|
+
})
|
|
28
|
+
.then(() => {
|
|
29
|
+
if (!cancelled)
|
|
30
|
+
onReady?.();
|
|
31
|
+
})
|
|
32
|
+
.catch((err) => {
|
|
33
|
+
if (cancelled)
|
|
34
|
+
return;
|
|
35
|
+
const message = err instanceof Error ? err.message : "Failed to load embed";
|
|
36
|
+
setError(message);
|
|
37
|
+
onError?.(err instanceof Error ? err : new Error(message));
|
|
38
|
+
});
|
|
39
|
+
return () => {
|
|
40
|
+
cancelled = true;
|
|
41
|
+
el.innerHTML = "";
|
|
42
|
+
};
|
|
43
|
+
}, [apiBaseUrl, embedToken, customerId, jwt, getJwt, uiConfig, onError, onReady]);
|
|
44
|
+
if (error) {
|
|
45
|
+
return (_jsx("div", { className: className, role: "alert", children: _jsx("p", { className: "p-4 text-sm text-red-600", children: error }) }));
|
|
46
|
+
}
|
|
47
|
+
return _jsx("div", { ref: mountRef, className: className, "aria-label": "Embedded analytics dashboard" });
|
|
48
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EmbeddedDashboardUiConfig } from "./ui.js";
|
|
2
|
+
export type AnalyticsEmbedSession = {
|
|
3
|
+
embedUrl: string;
|
|
4
|
+
guestToken: string;
|
|
5
|
+
embeddedDashboardId: string;
|
|
6
|
+
supersetDomain: string;
|
|
7
|
+
expiresInSeconds: number;
|
|
8
|
+
customerId: string;
|
|
9
|
+
tenantColumn: string;
|
|
10
|
+
rlsClause?: string;
|
|
11
|
+
embedUiConfig?: EmbeddedDashboardUiConfig;
|
|
12
|
+
hostedEmbedBaseUrl?: string;
|
|
13
|
+
hostedEmbedPath?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare class EmbedSessionError extends Error {
|
|
16
|
+
readonly status: number;
|
|
17
|
+
readonly code?: string;
|
|
18
|
+
constructor(message: string, status: number, code?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare function fetchAnalyticsEmbedSession(input: {
|
|
21
|
+
apiBaseUrl: string;
|
|
22
|
+
embedToken: string;
|
|
23
|
+
customerId: string;
|
|
24
|
+
jwt?: string;
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}): Promise<AnalyticsEmbedSession>;
|
|
27
|
+
export type EmbedSessionFetcherOptions = {
|
|
28
|
+
apiBaseUrl: string;
|
|
29
|
+
embedToken: string;
|
|
30
|
+
customerId: string;
|
|
31
|
+
/** Mint or return a workspace JWT (server-minted in Acme backend, passed to browser). */
|
|
32
|
+
getJwt?: () => Promise<string | undefined> | string | undefined;
|
|
33
|
+
};
|
|
34
|
+
/** Guest-token refresh callback for `@superset-ui/embedded-sdk`. */
|
|
35
|
+
export declare function createEmbedSessionFetcher(options: EmbedSessionFetcherOptions): () => Promise<string>;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { analyticsSessionApiUrl } from "./urls.js";
|
|
2
|
+
export class EmbedSessionError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, status, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "EmbedSessionError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function fetchAnalyticsEmbedSession(input) {
|
|
13
|
+
const url = analyticsSessionApiUrl(input.apiBaseUrl, input.embedToken, {
|
|
14
|
+
customerId: input.customerId,
|
|
15
|
+
jwt: input.jwt,
|
|
16
|
+
});
|
|
17
|
+
const res = await fetch(url, {
|
|
18
|
+
method: "GET",
|
|
19
|
+
cache: "no-store",
|
|
20
|
+
signal: input.signal,
|
|
21
|
+
});
|
|
22
|
+
const data = (await res.json());
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new EmbedSessionError(data.error || `Session request failed (${res.status})`, res.status, data.code);
|
|
25
|
+
}
|
|
26
|
+
if (!data.guestToken) {
|
|
27
|
+
throw new EmbedSessionError("Session response missing guestToken", 502);
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
/** Guest-token refresh callback for `@superset-ui/embedded-sdk`. */
|
|
32
|
+
export function createEmbedSessionFetcher(options) {
|
|
33
|
+
return async () => {
|
|
34
|
+
const jwt = options.getJwt ? await options.getJwt() : undefined;
|
|
35
|
+
const session = await fetchAnalyticsEmbedSession({
|
|
36
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
37
|
+
embedToken: options.embedToken,
|
|
38
|
+
customerId: options.customerId,
|
|
39
|
+
jwt,
|
|
40
|
+
});
|
|
41
|
+
return session.guestToken;
|
|
42
|
+
};
|
|
43
|
+
}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** White-label iframe chrome (matches control plane `embedUiConfig`). */
|
|
2
|
+
export type EmbeddedDashboardUiConfig = {
|
|
3
|
+
hideTitle: boolean;
|
|
4
|
+
hideTab: boolean;
|
|
5
|
+
hideChartControls: boolean;
|
|
6
|
+
filters: {
|
|
7
|
+
visible: boolean;
|
|
8
|
+
expanded: boolean;
|
|
9
|
+
};
|
|
10
|
+
urlParams?: Record<string, string>;
|
|
11
|
+
};
|
|
12
|
+
export declare const DEFAULT_EMBEDDED_DASHBOARD_UI_CONFIG: EmbeddedDashboardUiConfig;
|
package/dist/ui.js
ADDED
package/dist/urls.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type EmbedUrlParams = {
|
|
2
|
+
customerId?: string;
|
|
3
|
+
jwt?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function analyticsSessionApiPath(embedToken: string, params?: EmbedUrlParams): string;
|
|
6
|
+
export declare function analyticsSessionApiUrl(apiBaseUrl: string, embedToken: string, params?: EmbedUrlParams): string;
|
|
7
|
+
export declare function analyticsHostedEmbedPath(embedToken: string, params?: EmbedUrlParams): string;
|
|
8
|
+
export declare function analyticsHostedEmbedUrl(appBaseUrl: string, embedToken: string, params?: EmbedUrlParams): string;
|
package/dist/urls.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function analyticsSessionApiPath(embedToken, params) {
|
|
2
|
+
const q = new URLSearchParams();
|
|
3
|
+
if (params?.customerId)
|
|
4
|
+
q.set("customer_id", params.customerId);
|
|
5
|
+
if (params?.jwt)
|
|
6
|
+
q.set("jwt", params.jwt);
|
|
7
|
+
const qs = q.toString();
|
|
8
|
+
return `/embed/superset/${encodeURIComponent(embedToken)}/session${qs ? `?${qs}` : ""}`;
|
|
9
|
+
}
|
|
10
|
+
export function analyticsSessionApiUrl(apiBaseUrl, embedToken, params) {
|
|
11
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
12
|
+
return `${base}${analyticsSessionApiPath(embedToken, params)}`;
|
|
13
|
+
}
|
|
14
|
+
export function analyticsHostedEmbedPath(embedToken, params) {
|
|
15
|
+
const q = new URLSearchParams();
|
|
16
|
+
if (params?.customerId)
|
|
17
|
+
q.set("customer_id", params.customerId);
|
|
18
|
+
if (params?.jwt)
|
|
19
|
+
q.set("jwt", params.jwt);
|
|
20
|
+
const qs = q.toString();
|
|
21
|
+
return `/embed/analytics/${encodeURIComponent(embedToken)}${qs ? `?${qs}` : ""}`;
|
|
22
|
+
}
|
|
23
|
+
export function analyticsHostedEmbedUrl(appBaseUrl, embedToken, params) {
|
|
24
|
+
const base = appBaseUrl.replace(/\/$/, "");
|
|
25
|
+
return `${base}${analyticsHostedEmbedPath(embedToken, params)}`;
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@embeddedcanvas/embed-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Embedded Canvas SDK — JWT minting, session API, and analytics embed helpers for B2B SaaS integrators.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./jwt": {
|
|
15
|
+
"types": "./dist/jwt.d.ts",
|
|
16
|
+
"import": "./dist/jwt.js"
|
|
17
|
+
},
|
|
18
|
+
"./node": {
|
|
19
|
+
"types": "./dist/node.d.ts",
|
|
20
|
+
"import": "./dist/node.js"
|
|
21
|
+
},
|
|
22
|
+
"./react": {
|
|
23
|
+
"types": "./dist/react.d.ts",
|
|
24
|
+
"import": "./dist/react.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"prepack": "npm run build",
|
|
35
|
+
"prepublishOnly": "npm run build",
|
|
36
|
+
"test": "node --experimental-vm-modules --test dist/jwt.test.js 2>/dev/null || echo 'run build first'"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"embedded-analytics",
|
|
40
|
+
"embeddedcanvas",
|
|
41
|
+
"superset",
|
|
42
|
+
"dashboard",
|
|
43
|
+
"embed",
|
|
44
|
+
"b2b-saas"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@superset-ui/embedded-sdk": "^0.3.0",
|
|
55
|
+
"react": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@superset-ui/embedded-sdk": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"react": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@superset-ui/embedded-sdk": "^0.3.0",
|
|
67
|
+
"@types/node": "^20",
|
|
68
|
+
"@types/react": "^19",
|
|
69
|
+
"react": "19.2.3",
|
|
70
|
+
"typescript": "^5"
|
|
71
|
+
}
|
|
72
|
+
}
|