@basedash/embed 0.1.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.
@@ -0,0 +1,59 @@
1
+ import { EmbedUser, BasedashRole } from './index.cjs';
2
+
3
+ type EmbedSecret = string | Uint8Array;
4
+ /**
5
+ * A duration understood by `jose` (for example, `"10m"` or `"1h"`) or a
6
+ * duration in seconds.
7
+ */
8
+ type TokenExpiration = string | number;
9
+ type DashboardFilterValue = string | string[] | number | boolean;
10
+ interface CreateEmbedTokenOptions {
11
+ secret: EmbedSecret;
12
+ orgId: string;
13
+ user: EmbedUser;
14
+ /**
15
+ * @default "10m"
16
+ */
17
+ expiresIn?: TokenExpiration;
18
+ }
19
+ interface CreateDashboardFilterTokenOptions {
20
+ secret: EmbedSecret;
21
+ dashboardLinkId: string;
22
+ params: Record<string, DashboardFilterValue>;
23
+ /**
24
+ * @default "1h"
25
+ */
26
+ expiresIn?: TokenExpiration;
27
+ }
28
+ interface EmbedTokenClaims {
29
+ email: string;
30
+ orgId: string;
31
+ firstName?: string;
32
+ lastName?: string;
33
+ role?: BasedashRole;
34
+ groups?: string[];
35
+ iat: number;
36
+ exp: number;
37
+ }
38
+ interface DashboardFilterTokenClaims {
39
+ dashboardLinkId: string;
40
+ params: Record<string, DashboardFilterValue>;
41
+ iat: number;
42
+ exp: number;
43
+ }
44
+ /**
45
+ * Creates the short-lived JWT used by full-app Basedash embeds.
46
+ *
47
+ * This function must only run on a trusted server. Never send the embed secret
48
+ * to a browser.
49
+ */
50
+ declare function createEmbedToken({ secret, orgId, user, expiresIn, }: CreateEmbedTokenOptions): Promise<string>;
51
+ /**
52
+ * Creates a JWT that locks filter values on a shared dashboard.
53
+ *
54
+ * This function must only run on a trusted server. Never send the embed secret
55
+ * to a browser.
56
+ */
57
+ declare function createDashboardFilterToken({ secret, dashboardLinkId, params, expiresIn, }: CreateDashboardFilterTokenOptions): Promise<string>;
58
+
59
+ export { type CreateDashboardFilterTokenOptions, type CreateEmbedTokenOptions, type DashboardFilterTokenClaims, type DashboardFilterValue, type EmbedSecret, type EmbedTokenClaims, type TokenExpiration, createDashboardFilterToken, createEmbedToken };
@@ -0,0 +1,59 @@
1
+ import { EmbedUser, BasedashRole } from './index.js';
2
+
3
+ type EmbedSecret = string | Uint8Array;
4
+ /**
5
+ * A duration understood by `jose` (for example, `"10m"` or `"1h"`) or a
6
+ * duration in seconds.
7
+ */
8
+ type TokenExpiration = string | number;
9
+ type DashboardFilterValue = string | string[] | number | boolean;
10
+ interface CreateEmbedTokenOptions {
11
+ secret: EmbedSecret;
12
+ orgId: string;
13
+ user: EmbedUser;
14
+ /**
15
+ * @default "10m"
16
+ */
17
+ expiresIn?: TokenExpiration;
18
+ }
19
+ interface CreateDashboardFilterTokenOptions {
20
+ secret: EmbedSecret;
21
+ dashboardLinkId: string;
22
+ params: Record<string, DashboardFilterValue>;
23
+ /**
24
+ * @default "1h"
25
+ */
26
+ expiresIn?: TokenExpiration;
27
+ }
28
+ interface EmbedTokenClaims {
29
+ email: string;
30
+ orgId: string;
31
+ firstName?: string;
32
+ lastName?: string;
33
+ role?: BasedashRole;
34
+ groups?: string[];
35
+ iat: number;
36
+ exp: number;
37
+ }
38
+ interface DashboardFilterTokenClaims {
39
+ dashboardLinkId: string;
40
+ params: Record<string, DashboardFilterValue>;
41
+ iat: number;
42
+ exp: number;
43
+ }
44
+ /**
45
+ * Creates the short-lived JWT used by full-app Basedash embeds.
46
+ *
47
+ * This function must only run on a trusted server. Never send the embed secret
48
+ * to a browser.
49
+ */
50
+ declare function createEmbedToken({ secret, orgId, user, expiresIn, }: CreateEmbedTokenOptions): Promise<string>;
51
+ /**
52
+ * Creates a JWT that locks filter values on a shared dashboard.
53
+ *
54
+ * This function must only run on a trusted server. Never send the embed secret
55
+ * to a browser.
56
+ */
57
+ declare function createDashboardFilterToken({ secret, dashboardLinkId, params, expiresIn, }: CreateDashboardFilterTokenOptions): Promise<string>;
58
+
59
+ export { type CreateDashboardFilterTokenOptions, type CreateEmbedTokenOptions, type DashboardFilterTokenClaims, type DashboardFilterValue, type EmbedSecret, type EmbedTokenClaims, type TokenExpiration, createDashboardFilterToken, createEmbedToken };
package/dist/server.js ADDED
@@ -0,0 +1,83 @@
1
+ import { SignJWT } from 'jose';
2
+
3
+ // src/server/index.ts
4
+ var ALGORITHM = "HS256";
5
+ var DEFAULT_EMBED_TOKEN_EXPIRATION = "10m";
6
+ var DEFAULT_DASHBOARD_TOKEN_EXPIRATION = "1h";
7
+ async function createEmbedToken({
8
+ secret,
9
+ orgId,
10
+ user,
11
+ expiresIn = DEFAULT_EMBED_TOKEN_EXPIRATION
12
+ }) {
13
+ assertSecret(secret);
14
+ assertNonEmpty(orgId, "orgId");
15
+ assertNonEmpty(user.email, "user.email");
16
+ assertExpiration(expiresIn);
17
+ const payload = {
18
+ email: user.email,
19
+ orgId,
20
+ ...user.firstName === void 0 ? {} : { firstName: user.firstName },
21
+ ...user.lastName === void 0 ? {} : { lastName: user.lastName },
22
+ ...user.role === void 0 ? {} : { role: user.role },
23
+ ...user.groups === void 0 ? {} : { groups: user.groups }
24
+ };
25
+ return signToken(payload, secret, expiresIn);
26
+ }
27
+ async function createDashboardFilterToken({
28
+ secret,
29
+ dashboardLinkId,
30
+ params,
31
+ expiresIn = DEFAULT_DASHBOARD_TOKEN_EXPIRATION
32
+ }) {
33
+ assertSecret(secret);
34
+ assertNonEmpty(dashboardLinkId, "dashboardLinkId");
35
+ assertExpiration(expiresIn);
36
+ assertDashboardParams(params);
37
+ return signToken(
38
+ {
39
+ dashboardLinkId,
40
+ params
41
+ },
42
+ secret,
43
+ expiresIn
44
+ );
45
+ }
46
+ async function signToken(payload, secret, expiresIn) {
47
+ const issuedAt = Math.floor(Date.now() / 1e3);
48
+ const expiration = typeof expiresIn === "number" ? issuedAt + expiresIn : expiresIn;
49
+ return new SignJWT(payload).setProtectedHeader({ alg: ALGORITHM, typ: "JWT" }).setIssuedAt(issuedAt).setExpirationTime(expiration).sign(toSecretKey(secret));
50
+ }
51
+ function toSecretKey(secret) {
52
+ return typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
53
+ }
54
+ function assertSecret(secret) {
55
+ if (typeof secret === "string" && secret.length === 0 || secret instanceof Uint8Array && secret.byteLength === 0) {
56
+ throw new TypeError("secret must not be empty");
57
+ }
58
+ }
59
+ function assertExpiration(expiresIn) {
60
+ if (typeof expiresIn === "string" && expiresIn.trim().length === 0 || typeof expiresIn === "number" && (!Number.isFinite(expiresIn) || expiresIn <= 0)) {
61
+ throw new TypeError("expiresIn must be a positive duration");
62
+ }
63
+ }
64
+ function assertDashboardParams(params) {
65
+ for (const [key, value] of Object.entries(params)) {
66
+ assertNonEmpty(key, "params key");
67
+ const validValue = typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((item) => typeof item === "string");
68
+ if (!validValue) {
69
+ throw new TypeError(
70
+ `params.${key} must be a string, string array, finite number, or boolean`
71
+ );
72
+ }
73
+ }
74
+ }
75
+ function assertNonEmpty(value, name) {
76
+ if (value.trim().length === 0) {
77
+ throw new TypeError(`${name} must not be empty`);
78
+ }
79
+ }
80
+
81
+ export { createDashboardFilterToken, createEmbedToken };
82
+ //# sourceMappingURL=server.js.map
83
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/index.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["import { SignJWT } from \"jose\";\n\nimport type { BasedashRole, EmbedUser } from \"../embed\";\n\nconst ALGORITHM = \"HS256\";\nconst DEFAULT_EMBED_TOKEN_EXPIRATION = \"10m\";\nconst DEFAULT_DASHBOARD_TOKEN_EXPIRATION = \"1h\";\n\nexport type EmbedSecret = string | Uint8Array;\n\n/**\n * A duration understood by `jose` (for example, `\"10m\"` or `\"1h\"`) or a\n * duration in seconds.\n */\nexport type TokenExpiration = string | number;\n\nexport type DashboardFilterValue = string | string[] | number | boolean;\n\nexport interface CreateEmbedTokenOptions {\n secret: EmbedSecret;\n orgId: string;\n user: EmbedUser;\n /**\n * @default \"10m\"\n */\n expiresIn?: TokenExpiration;\n}\n\nexport interface CreateDashboardFilterTokenOptions {\n secret: EmbedSecret;\n dashboardLinkId: string;\n params: Record<string, DashboardFilterValue>;\n /**\n * @default \"1h\"\n */\n expiresIn?: TokenExpiration;\n}\n\nexport interface EmbedTokenClaims {\n email: string;\n orgId: string;\n firstName?: string;\n lastName?: string;\n role?: BasedashRole;\n groups?: string[];\n iat: number;\n exp: number;\n}\n\nexport interface DashboardFilterTokenClaims {\n dashboardLinkId: string;\n params: Record<string, DashboardFilterValue>;\n iat: number;\n exp: number;\n}\n\n/**\n * Creates the short-lived JWT used by full-app Basedash embeds.\n *\n * This function must only run on a trusted server. Never send the embed secret\n * to a browser.\n */\nexport async function createEmbedToken({\n secret,\n orgId,\n user,\n expiresIn = DEFAULT_EMBED_TOKEN_EXPIRATION,\n}: CreateEmbedTokenOptions): Promise<string> {\n assertSecret(secret);\n assertNonEmpty(orgId, \"orgId\");\n assertNonEmpty(user.email, \"user.email\");\n assertExpiration(expiresIn);\n\n const payload = {\n email: user.email,\n orgId,\n ...(user.firstName === undefined ? {} : { firstName: user.firstName }),\n ...(user.lastName === undefined ? {} : { lastName: user.lastName }),\n ...(user.role === undefined ? {} : { role: user.role }),\n ...(user.groups === undefined ? {} : { groups: user.groups }),\n };\n\n return signToken(payload, secret, expiresIn);\n}\n\n/**\n * Creates a JWT that locks filter values on a shared dashboard.\n *\n * This function must only run on a trusted server. Never send the embed secret\n * to a browser.\n */\nexport async function createDashboardFilterToken({\n secret,\n dashboardLinkId,\n params,\n expiresIn = DEFAULT_DASHBOARD_TOKEN_EXPIRATION,\n}: CreateDashboardFilterTokenOptions): Promise<string> {\n assertSecret(secret);\n assertNonEmpty(dashboardLinkId, \"dashboardLinkId\");\n assertExpiration(expiresIn);\n assertDashboardParams(params);\n\n return signToken(\n {\n dashboardLinkId,\n params,\n },\n secret,\n expiresIn,\n );\n}\n\nasync function signToken(\n payload: Record<string, unknown>,\n secret: EmbedSecret,\n expiresIn: TokenExpiration,\n): Promise<string> {\n const issuedAt = Math.floor(Date.now() / 1000);\n const expiration =\n typeof expiresIn === \"number\" ? issuedAt + expiresIn : expiresIn;\n\n return new SignJWT(payload)\n .setProtectedHeader({ alg: ALGORITHM, typ: \"JWT\" })\n .setIssuedAt(issuedAt)\n .setExpirationTime(expiration)\n .sign(toSecretKey(secret));\n}\n\nfunction toSecretKey(secret: EmbedSecret): Uint8Array {\n return typeof secret === \"string\" ? new TextEncoder().encode(secret) : secret;\n}\n\nfunction assertSecret(secret: EmbedSecret): void {\n if (\n (typeof secret === \"string\" && secret.length === 0) ||\n (secret instanceof Uint8Array && secret.byteLength === 0)\n ) {\n throw new TypeError(\"secret must not be empty\");\n }\n}\n\nfunction assertExpiration(expiresIn: TokenExpiration): void {\n if (\n (typeof expiresIn === \"string\" && expiresIn.trim().length === 0) ||\n (typeof expiresIn === \"number\" &&\n (!Number.isFinite(expiresIn) || expiresIn <= 0))\n ) {\n throw new TypeError(\"expiresIn must be a positive duration\");\n }\n}\n\nfunction assertDashboardParams(\n params: Record<string, DashboardFilterValue>,\n): void {\n for (const [key, value] of Object.entries(params)) {\n assertNonEmpty(key, \"params key\");\n\n const validValue =\n typeof value === \"string\" ||\n typeof value === \"boolean\" ||\n (typeof value === \"number\" && Number.isFinite(value)) ||\n (Array.isArray(value) &&\n value.every((item): item is string => typeof item === \"string\"));\n\n if (!validValue) {\n throw new TypeError(\n `params.${key} must be a string, string array, finite number, or boolean`,\n );\n }\n }\n}\n\nfunction assertNonEmpty(value: string, name: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`${name} must not be empty`);\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "@basedash/embed",
3
+ "version": "0.1.0",
4
+ "description": "Typed helpers and React components for embedding Basedash",
5
+ "license": "MIT",
6
+ "author": "Basedash",
7
+ "homepage": "https://www.basedash.com/docs/features/embedding",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Basedash/embed.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Basedash/embed/issues"
14
+ },
15
+ "keywords": [
16
+ "basedash",
17
+ "analytics",
18
+ "embedding",
19
+ "iframe",
20
+ "react"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "import": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "require": {
34
+ "types": "./dist/index.d.cts",
35
+ "default": "./dist/index.cjs"
36
+ }
37
+ },
38
+ "./server": {
39
+ "import": {
40
+ "types": "./dist/server.d.ts",
41
+ "default": "./dist/server.js"
42
+ },
43
+ "require": {
44
+ "types": "./dist/server.d.cts",
45
+ "default": "./dist/server.cjs"
46
+ }
47
+ },
48
+ "./react": {
49
+ "import": {
50
+ "types": "./dist/react.d.ts",
51
+ "default": "./dist/react.js"
52
+ },
53
+ "require": {
54
+ "types": "./dist/react.d.cts",
55
+ "default": "./dist/react.cjs"
56
+ }
57
+ },
58
+ "./package.json": "./package.json"
59
+ },
60
+ "files": [
61
+ "dist",
62
+ "README.md",
63
+ "LICENSE"
64
+ ],
65
+ "engines": {
66
+ "node": ">=20"
67
+ },
68
+ "packageManager": "pnpm@10.33.0",
69
+ "scripts": {
70
+ "build": "tsup",
71
+ "check": "pnpm typecheck && pnpm test && pnpm build && publint && attw --pack . --profile node16",
72
+ "changeset": "changeset",
73
+ "dev": "tsup --watch",
74
+ "lint": "pnpm typecheck",
75
+ "prepublishOnly": "pnpm check",
76
+ "test": "vitest run",
77
+ "test:watch": "vitest",
78
+ "typecheck": "tsc --noEmit"
79
+ },
80
+ "publishConfig": {
81
+ "access": "public",
82
+ "provenance": true
83
+ },
84
+ "dependencies": {
85
+ "jose": "^6.2.10"
86
+ },
87
+ "peerDependencies": {
88
+ "react": ">=18.2.0 <20"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "react": {
92
+ "optional": true
93
+ }
94
+ },
95
+ "devDependencies": {
96
+ "@arethetypeswrong/cli": "^0.18.5",
97
+ "@changesets/cli": "^3.0.1",
98
+ "@testing-library/react": "^16.3.2",
99
+ "@types/react": "^19.2.18",
100
+ "@types/react-dom": "^19.2.5",
101
+ "jsdom": "^30.0.1",
102
+ "publint": "^0.3.24",
103
+ "react": "^19.2.8",
104
+ "react-dom": "^19.2.8",
105
+ "tsup": "^8.5.1",
106
+ "typescript": "^5.9.3",
107
+ "vitest": "^4.1.11"
108
+ },
109
+ "pnpm": {
110
+ "onlyBuiltDependencies": [
111
+ "esbuild"
112
+ ]
113
+ }
114
+ }