@bloomscorp/blooms-ai-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.
- package/CHANGELOG.md +20 -0
- package/README.md +456 -0
- package/dist/blooms-embed.css +1 -0
- package/dist/blooms-embed.js +10 -0
- package/dist/browser-guard.js +23 -0
- package/dist/index.d.ts +284 -0
- package/dist/index.js +37 -0
- package/package.json +54 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for `@bloomscorp/blooms-ai-embed`.
|
|
3
|
+
*
|
|
4
|
+
* Hand-written to mirror the runtime exactly (the bundle ships no declarations
|
|
5
|
+
* of its own). Kept in step with, in the blooms.ai repo:
|
|
6
|
+
* client/src/app/core/embed/embed-config.ts — config, modes, storage
|
|
7
|
+
* client/src/app/core/embed/embed-session.store.ts — session, user, events
|
|
8
|
+
* client/src/app/core/embed/embed-api.client.ts — manifest
|
|
9
|
+
* client/src/app/core/embed/blooms-embed.api.ts — the SDK surface
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ── Configuration ───────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/** The resolved rendering mode. `mode()` never reports `'auto'`. */
|
|
15
|
+
export type EmbedMode = 'element' | 'iframe';
|
|
16
|
+
|
|
17
|
+
/** What `configure()` accepts for `mode`. `'auto'` is the default. */
|
|
18
|
+
export type EmbedModeSetting = EmbedMode | 'auto';
|
|
19
|
+
|
|
20
|
+
/** Where the session is kept between page loads. */
|
|
21
|
+
export type EmbedStorage = 'local' | 'session' | 'memory';
|
|
22
|
+
|
|
23
|
+
export interface BloomsEmbedConfig {
|
|
24
|
+
/** The publishable project embed token: `blm_embed_…`. Required. */
|
|
25
|
+
token: string;
|
|
26
|
+
/** Absolute origin of the blooms.ai API. Derived from the bundle URL when omitted. */
|
|
27
|
+
apiBase?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Absolute URL of the DIRECTORY holding `blooms-embed.css`, used to load the
|
|
30
|
+
* shared stylesheet that is adopted into each component's shadow root.
|
|
31
|
+
*
|
|
32
|
+
* Defaults to the directory the bundle's own JavaScript was served from, which
|
|
33
|
+
* is correct when you load the bundle from blooms.ai. When you install from npm
|
|
34
|
+
* your bundler inlines the bundle into your own output, so the default resolves
|
|
35
|
+
* to YOUR build directory — where the stylesheet is not. Copy
|
|
36
|
+
* `blooms-embed.css` somewhere your app serves and name that directory here.
|
|
37
|
+
*/
|
|
38
|
+
assetBase?: string;
|
|
39
|
+
/** Where the session is kept. Default `'local'`. */
|
|
40
|
+
storage?: EmbedStorage;
|
|
41
|
+
/** Rendering mode. Default `'auto'`. */
|
|
42
|
+
mode?: EmbedModeSetting;
|
|
43
|
+
/** CSS custom properties applied to each element's shadow root. */
|
|
44
|
+
theme?: Record<string, string>;
|
|
45
|
+
/**
|
|
46
|
+
* Set by the iframe shell only, never by a host: the parent dashboard's origin
|
|
47
|
+
* as verified server-side, forwarded so the audit trail names the dashboard the
|
|
48
|
+
* user was working in.
|
|
49
|
+
*/
|
|
50
|
+
parentOrigin?: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A config after defaulting and auto-detection. Returned by nothing on the
|
|
55
|
+
* public surface today; exported because it is the shape the SDK works with
|
|
56
|
+
* internally and hosts occasionally want to name it.
|
|
57
|
+
*/
|
|
58
|
+
export interface ResolvedEmbedConfig extends Required<Omit<BloomsEmbedConfig, 'theme'>> {
|
|
59
|
+
theme: Record<string, string>;
|
|
60
|
+
mode: EmbedMode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Session ─────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export interface EmbedUser {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
email: string;
|
|
69
|
+
avatar: string | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface EmbedProject {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* One project a session may act on, with what the signed-in user can do in it.
|
|
79
|
+
*
|
|
80
|
+
* `grantedKeys` has already been intersected with the integration's ceiling by
|
|
81
|
+
* the server, so it is exactly what this user may use in this project — grants
|
|
82
|
+
* differ per project, which is why they are listed per entry rather than once.
|
|
83
|
+
*/
|
|
84
|
+
export interface EmbedProjectAccess extends EmbedProject {
|
|
85
|
+
grantedKeys: string[];
|
|
86
|
+
isOwner: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface EmbedSessionState {
|
|
90
|
+
/** Access token. Sent by the elements automatically; hosts rarely need it. */
|
|
91
|
+
token: string;
|
|
92
|
+
refreshToken: string;
|
|
93
|
+
/** Epoch ms at which the access token expires. */
|
|
94
|
+
expiresAt: number;
|
|
95
|
+
user: EmbedUser;
|
|
96
|
+
project: EmbedProject;
|
|
97
|
+
/** Feature keys this user may use in the ACTIVE project, already intersected with the token's ceiling. */
|
|
98
|
+
grantedKeys: string[];
|
|
99
|
+
isProjectOwner: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Every project this integration authorizes AND the user can reach, each with
|
|
102
|
+
* its own grants. One entry for a single-project integration.
|
|
103
|
+
*
|
|
104
|
+
* Optional because a session persisted by an older bundle will not have it;
|
|
105
|
+
* readers fall back to a one-entry list built from `project`.
|
|
106
|
+
*/
|
|
107
|
+
projects?: EmbedProjectAccess[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Events ──────────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/** Sign-in, refresh, sign-out and expiry. `session` is `null` when signed out. */
|
|
113
|
+
export interface EmbedSessionEvent {
|
|
114
|
+
type: 'session';
|
|
115
|
+
session: EmbedSessionState | null;
|
|
116
|
+
mode: EmbedMode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A failure worth surfacing to the host, e.g. `EMBED_TOKEN_REVOKED`. */
|
|
120
|
+
export interface EmbedErrorEvent {
|
|
121
|
+
type: 'error';
|
|
122
|
+
code: string;
|
|
123
|
+
message: string;
|
|
124
|
+
status?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A component asking the host to move somewhere, for hosts that mirror state in their own URL. */
|
|
128
|
+
export interface EmbedNavigateEvent {
|
|
129
|
+
type: 'navigate';
|
|
130
|
+
feature: string;
|
|
131
|
+
view: string;
|
|
132
|
+
params: Record<string, unknown>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type EmbedEvent = EmbedSessionEvent | EmbedErrorEvent | EmbedNavigateEvent;
|
|
136
|
+
|
|
137
|
+
// ── Manifest ────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/** Pre-login bootstrap: project name, enabled features, theme, branding. */
|
|
140
|
+
export interface EmbedManifest {
|
|
141
|
+
/** The primary project. Equal to `projects[0]` for a multi-project integration. */
|
|
142
|
+
project: { id: string; name: string };
|
|
143
|
+
/**
|
|
144
|
+
* Every project this integration declares, names only.
|
|
145
|
+
*
|
|
146
|
+
* Pre-login, so this is the integration's declared SCOPE, not anything the
|
|
147
|
+
* caller is entitled to — which of these a given user can actually reach is
|
|
148
|
+
* decided at login and reported as `session.projects`.
|
|
149
|
+
*/
|
|
150
|
+
projects: Array<{ id: string; name: string }>;
|
|
151
|
+
/** The integration's feature ceiling. Empty means "whatever the user already has". */
|
|
152
|
+
features: string[];
|
|
153
|
+
theme: Record<string, string>;
|
|
154
|
+
hideBranding: boolean;
|
|
155
|
+
sessionTtlMinutes: number;
|
|
156
|
+
/** Where to send someone who cannot sign in — opens the blooms.ai portal. */
|
|
157
|
+
portalUrl: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── SDK ─────────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
export interface MountOptions {
|
|
163
|
+
/** Which component to mount, e.g. `'login'` for `<blooms-login>`. */
|
|
164
|
+
feature: string;
|
|
165
|
+
/** Initial height in px before the frame reports its own. Iframe mode only. */
|
|
166
|
+
minHeight?: number;
|
|
167
|
+
/** Extra attributes for the created element or iframe (`title`, `class`, ...). */
|
|
168
|
+
attributes?: Record<string, string>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Handle returned by `mount()` in iframe mode. */
|
|
172
|
+
export interface MountedFrame {
|
|
173
|
+
readonly iframe: HTMLIFrameElement;
|
|
174
|
+
/** Push the current session into the frame. Done automatically on handshake. */
|
|
175
|
+
sync(): void;
|
|
176
|
+
destroy(): void;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface BloomsEmbedSdk {
|
|
180
|
+
readonly version: string;
|
|
181
|
+
/** Must be called once, before anything else. Throws on a missing or non-`blm_embed_` token. */
|
|
182
|
+
configure(config: BloomsEmbedConfig): void;
|
|
183
|
+
/** The resolved rendering mode, after auto-detection. `null` before `configure()`. */
|
|
184
|
+
mode(): EmbedMode | null;
|
|
185
|
+
manifest(): Promise<EmbedManifest>;
|
|
186
|
+
login(credentials: { email: string; password: string }): Promise<EmbedSessionState>;
|
|
187
|
+
/** Current session, or `null`. Restored from storage by `configure()`. */
|
|
188
|
+
session(): EmbedSessionState | null;
|
|
189
|
+
/** Revalidate a restored session against the server and re-read permissions. */
|
|
190
|
+
resume(): Promise<EmbedSessionState | null>;
|
|
191
|
+
/**
|
|
192
|
+
* Can the signed-in user use a feature? Defaults to the active project; pass
|
|
193
|
+
* `projectId` to ask about another project the integration authorizes, since
|
|
194
|
+
* grants differ per project.
|
|
195
|
+
*
|
|
196
|
+
* Returns `false` for a project the session cannot act on — it does NOT fall
|
|
197
|
+
* back to the active project's grants.
|
|
198
|
+
*
|
|
199
|
+
* A UI convenience only: the server re-authorizes every request.
|
|
200
|
+
*/
|
|
201
|
+
can(featureKey: string, projectId?: string): boolean;
|
|
202
|
+
/** Every project this session can act on, each with its own grants. */
|
|
203
|
+
projects(): EmbedProjectAccess[];
|
|
204
|
+
/** The active project, or `null` when not signed in. */
|
|
205
|
+
project(): EmbedProject | null;
|
|
206
|
+
/**
|
|
207
|
+
* Switch the active project. Local and instant — the session already covers
|
|
208
|
+
* every project `projects()` lists, so there is no round trip and no
|
|
209
|
+
* re-authentication. Returns `false` if the session cannot act on it.
|
|
210
|
+
*
|
|
211
|
+
* Emits a `session` event, so an `on('session', …)` listener re-renders on a
|
|
212
|
+
* switch without a separate subscription.
|
|
213
|
+
*/
|
|
214
|
+
setProject(projectId: string): boolean;
|
|
215
|
+
logout(): Promise<void>;
|
|
216
|
+
/** Subscribe. Returns an unsubscribe function. */
|
|
217
|
+
on(event: EmbedEvent['type'] | 'all', handler: (event: EmbedEvent) => void): () => void;
|
|
218
|
+
/**
|
|
219
|
+
* Render a component into `target`.
|
|
220
|
+
*
|
|
221
|
+
* Element mode creates the custom element and returns it. Iframe mode creates
|
|
222
|
+
* the frame, drives the postMessage bridge, and returns a `MountedFrame`. The
|
|
223
|
+
* call is the same either way — the mode is resolved by `configure()`.
|
|
224
|
+
*/
|
|
225
|
+
mount(target: HTMLElement | string, options: MountOptions): MountedFrame | HTMLElement;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The SDK. Importing this package defines the custom elements and installs the
|
|
230
|
+
* same object on `window.BloomsEmbed`.
|
|
231
|
+
*/
|
|
232
|
+
export const BloomsEmbed: BloomsEmbedSdk;
|
|
233
|
+
|
|
234
|
+
// ── Elements ────────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* `<blooms-login>`.
|
|
238
|
+
*
|
|
239
|
+
* The three inputs are also settable as attributes, but attribute values arrive
|
|
240
|
+
* as strings — `compact="false"` is a non-empty string and therefore truthy, so
|
|
241
|
+
* set `compact` as a property (`el.compact = true`) or leave the attribute off.
|
|
242
|
+
*/
|
|
243
|
+
export interface BloomsLoginElement extends HTMLElement {
|
|
244
|
+
/** Overrides the default heading copy. */
|
|
245
|
+
heading: string;
|
|
246
|
+
/** Overrides the default subheading copy ("Continue to <project>"). */
|
|
247
|
+
subheading: string;
|
|
248
|
+
/** Render without the card chrome, for placing inside an existing panel. */
|
|
249
|
+
compact: boolean;
|
|
250
|
+
|
|
251
|
+
addEventListener(
|
|
252
|
+
type: 'bloomssession',
|
|
253
|
+
listener: (event: CustomEvent<EmbedSessionState | null>) => void,
|
|
254
|
+
options?: boolean | AddEventListenerOptions,
|
|
255
|
+
): void;
|
|
256
|
+
addEventListener(
|
|
257
|
+
type: 'bloomserror',
|
|
258
|
+
listener: (event: CustomEvent<EmbedErrorEvent>) => void,
|
|
259
|
+
options?: boolean | AddEventListenerOptions,
|
|
260
|
+
): void;
|
|
261
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
262
|
+
type: K,
|
|
263
|
+
listener: (this: HTMLElement, event: HTMLElementEventMap[K]) => unknown,
|
|
264
|
+
options?: boolean | AddEventListenerOptions,
|
|
265
|
+
): void;
|
|
266
|
+
addEventListener(
|
|
267
|
+
type: string,
|
|
268
|
+
listener: EventListenerOrEventListenerObject,
|
|
269
|
+
options?: boolean | AddEventListenerOptions,
|
|
270
|
+
): void;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
declare global {
|
|
274
|
+
interface Window {
|
|
275
|
+
/** Installed by the bundle as it evaluates, i.e. by importing this package. */
|
|
276
|
+
BloomsEmbed: BloomsEmbedSdk;
|
|
277
|
+
/** Double-load marker set by the bundle. Internal; read it only to detect a duplicate include. */
|
|
278
|
+
__BLOOMS_EMBED_VERSION__?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
interface HTMLElementTagNameMap {
|
|
282
|
+
'blooms-login': BloomsLoginElement;
|
|
283
|
+
}
|
|
284
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@bloomscorp/blooms-ai-embed` — the blooms.ai embeddable web components.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module has two effects:
|
|
5
|
+
* 1. it defines the custom elements (`<blooms-login>`, …) on the page, and
|
|
6
|
+
* 2. it installs and re-exports the `BloomsEmbed` SDK object.
|
|
7
|
+
*
|
|
8
|
+
* Both come from the prebuilt bundle, which self-registers when it evaluates.
|
|
9
|
+
* This entry deliberately adds no behaviour of its own — the bundle is the
|
|
10
|
+
* single implementation, shared with the CDN-hosted bundle at
|
|
11
|
+
* https://embed-ai.bloomscorp.com/v1/blooms-embed.js.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Must stay first: it fails loudly outside a browser before the bundle runs.
|
|
15
|
+
import './browser-guard.js';
|
|
16
|
+
// Side-effect import: defines the custom elements and sets window.BloomsEmbed.
|
|
17
|
+
import './blooms-embed.js';
|
|
18
|
+
|
|
19
|
+
const sdk = /** @type {any} */ (window).BloomsEmbed;
|
|
20
|
+
|
|
21
|
+
if (!sdk) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'[BloomsEmbed] The bundle loaded but did not install window.BloomsEmbed. ' +
|
|
24
|
+
'This usually means a second, incompatible copy of the embed bundle was already on the page ' +
|
|
25
|
+
'(check for a <script src="https://embed-ai.bloomscorp.com/v1/blooms-embed.js"> alongside '
|
|
26
|
+
+ 'this package — include it once, either way, never both).',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The SDK object. Read from `window` on purpose: if a page somehow ends up with
|
|
32
|
+
* two copies of the bundle, the first one wins and everybody shares its session
|
|
33
|
+
* store instead of two stores fighting over the same storage key.
|
|
34
|
+
*
|
|
35
|
+
* @type {import('./index.d.ts').BloomsEmbedSdk}
|
|
36
|
+
*/
|
|
37
|
+
export const BloomsEmbed = sdk;
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bloomscorp/blooms-ai-embed",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "blooms.ai embeddable web components \u2014 prebuilt bundle and typed JS SDK for host dashboards, including Electron.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": true,
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./blooms-embed.css": "./dist/blooms-embed.css",
|
|
18
|
+
"./blooms-embed.js": "./dist/blooms-embed.js",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/index.js",
|
|
23
|
+
"dist/index.d.ts",
|
|
24
|
+
"dist/browser-guard.js",
|
|
25
|
+
"dist/blooms-embed.js",
|
|
26
|
+
"dist/blooms-embed.css",
|
|
27
|
+
"README.md",
|
|
28
|
+
"CHANGELOG.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "node scripts/build.mjs",
|
|
32
|
+
"prepack": "node scripts/build.mjs --skip-client-build"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"blooms",
|
|
36
|
+
"embed",
|
|
37
|
+
"web-components",
|
|
38
|
+
"custom-elements",
|
|
39
|
+
"analytics",
|
|
40
|
+
"electron"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/bloomscorp/blooms-ai.git",
|
|
51
|
+
"directory": "embed-sdk"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/bloomscorp/blooms-ai/tree/master/embed-sdk#readme"
|
|
54
|
+
}
|