@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.
@@ -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
+ }