@businessdash/sdk 0.9.60 → 0.9.80
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/README.md +991 -295
- package/assets/biab-sw.js +92 -0
- package/dist/account-core/index.d.ts +126 -0
- package/dist/account-core/index.d.ts.map +1 -0
- package/dist/account-core/index.js +191 -0
- package/dist/account-core/index.js.map +1 -0
- package/dist/account.d.ts +11 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +11 -0
- package/dist/account.js.map +1 -0
- package/dist/auth-handler.d.ts +1 -1
- package/dist/auth-links.d.ts +33 -0
- package/dist/auth-links.d.ts.map +1 -0
- package/dist/auth-links.js +64 -0
- package/dist/auth-links.js.map +1 -0
- package/dist/blog-core/index.d.ts +112 -0
- package/dist/blog-core/index.d.ts.map +1 -0
- package/dist/blog-core/index.js +330 -0
- package/dist/blog-core/index.js.map +1 -0
- package/dist/blog.d.ts +11 -0
- package/dist/blog.d.ts.map +1 -0
- package/dist/blog.js +11 -0
- package/dist/blog.js.map +1 -0
- package/dist/chat-core/index.d.ts +97 -0
- package/dist/chat-core/index.d.ts.map +1 -0
- package/dist/chat-core/index.js +237 -0
- package/dist/chat-core/index.js.map +1 -0
- package/dist/chat.d.ts +11 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +11 -0
- package/dist/chat.js.map +1 -0
- package/dist/checkout-core/index.d.ts +65 -0
- package/dist/checkout-core/index.d.ts.map +1 -0
- package/dist/checkout-core/index.js +159 -0
- package/dist/checkout-core/index.js.map +1 -0
- package/dist/checkout.d.ts +10 -0
- package/dist/checkout.d.ts.map +1 -0
- package/dist/checkout.js +10 -0
- package/dist/checkout.js.map +1 -0
- package/dist/cli.js +133 -25
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +622 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +888 -1
- package/dist/client.js.map +1 -1
- package/dist/consent-core/index.cjs +107 -0
- package/dist/consent-core/index.d.ts +103 -0
- package/dist/consent-core/index.d.ts.map +1 -0
- package/dist/consent-core/index.js +157 -0
- package/dist/consent-core/index.js.map +1 -0
- package/dist/contracts.d.ts +3526 -767
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +571 -4
- package/dist/contracts.js.map +1 -1
- package/dist/data-model-schema.d.ts +1 -1
- package/dist/data-model-schema.d.ts.map +1 -1
- package/dist/element.d.ts +30 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +56 -0
- package/dist/element.js.map +1 -1
- package/dist/embed-protocol.d.ts +86 -0
- package/dist/embed-protocol.d.ts.map +1 -0
- package/dist/embed-protocol.js +60 -0
- package/dist/embed-protocol.js.map +1 -0
- package/dist/framework-support.d.ts +122 -0
- package/dist/framework-support.d.ts.map +1 -0
- package/dist/framework-support.js +323 -0
- package/dist/framework-support.js.map +1 -0
- package/dist/index.cjs +1355 -24
- package/dist/legal-core/index.cjs +115 -0
- package/dist/legal-core/index.d.ts +160 -0
- package/dist/legal-core/index.d.ts.map +1 -0
- package/dist/legal-core/index.js +220 -0
- package/dist/legal-core/index.js.map +1 -0
- package/dist/marketing-core/index.d.ts +66 -0
- package/dist/marketing-core/index.d.ts.map +1 -0
- package/dist/marketing-core/index.js +154 -0
- package/dist/marketing-core/index.js.map +1 -0
- package/dist/marketing.d.ts +7 -0
- package/dist/marketing.d.ts.map +1 -0
- package/dist/marketing.js +7 -0
- package/dist/marketing.js.map +1 -0
- package/dist/mcp-adapters/index.d.ts +135 -0
- package/dist/mcp-adapters/index.d.ts.map +1 -0
- package/dist/mcp-adapters/index.js +187 -0
- package/dist/mcp-adapters/index.js.map +1 -0
- package/dist/mcp-adapters.d.ts +7 -0
- package/dist/mcp-adapters.d.ts.map +1 -0
- package/dist/mcp-adapters.js +7 -0
- package/dist/mcp-adapters.js.map +1 -0
- package/dist/model3d-core/index.cjs +95 -0
- package/dist/model3d-core/index.d.ts +167 -0
- package/dist/model3d-core/index.d.ts.map +1 -0
- package/dist/model3d-core/index.js +158 -0
- package/dist/model3d-core/index.js.map +1 -0
- package/dist/notifications-core/index.cjs +315 -0
- package/dist/notifications-core/index.d.ts +229 -0
- package/dist/notifications-core/index.d.ts.map +1 -0
- package/dist/notifications-core/index.js +445 -0
- package/dist/notifications-core/index.js.map +1 -0
- package/dist/portal-core/index.d.ts +451 -0
- package/dist/portal-core/index.d.ts.map +1 -0
- package/dist/portal-core/index.js +819 -0
- package/dist/portal-core/index.js.map +1 -0
- package/dist/portal.d.ts +7 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/portal.js +7 -0
- package/dist/portal.js.map +1 -0
- package/dist/push-core/index.cjs +171 -0
- package/dist/push-core/index.d.ts +149 -0
- package/dist/push-core/index.d.ts.map +1 -0
- package/dist/push-core/index.js +231 -0
- package/dist/push-core/index.js.map +1 -0
- package/dist/react-marketing.d.ts +1 -17
- package/dist/react-marketing.d.ts.map +1 -1
- package/dist/react-marketing.js +4 -0
- package/dist/react-marketing.js.map +1 -1
- package/dist/react.d.ts +294 -101
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +302 -246
- package/dist/react.js.map +1 -1
- package/dist/seed-core/index.d.ts +354 -0
- package/dist/seed-core/index.d.ts.map +1 -0
- package/dist/seed-core/index.js +420 -0
- package/dist/seed-core/index.js.map +1 -0
- package/dist/seed.d.ts +7 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +7 -0
- package/dist/seed.js.map +1 -0
- package/dist/seo-core/blog.d.ts +94 -0
- package/dist/seo-core/blog.d.ts.map +1 -0
- package/dist/seo-core/blog.js +149 -0
- package/dist/seo-core/blog.js.map +1 -0
- package/dist/seo-core/commerce.d.ts +64 -0
- package/dist/seo-core/commerce.d.ts.map +1 -0
- package/dist/seo-core/commerce.js +87 -0
- package/dist/seo-core/commerce.js.map +1 -0
- package/dist/seo-core/index.cjs +751 -0
- package/dist/seo-core/index.d.ts +226 -0
- package/dist/seo-core/index.d.ts.map +1 -0
- package/dist/seo-core/index.js +455 -0
- package/dist/seo-core/index.js.map +1 -0
- package/dist/seo-core/local.d.ts +181 -0
- package/dist/seo-core/local.d.ts.map +1 -0
- package/dist/seo-core/local.js +193 -0
- package/dist/seo-core/local.js.map +1 -0
- package/dist/sitemap-core/index.cjs +167 -0
- package/dist/sitemap-core/index.d.ts +155 -0
- package/dist/sitemap-core/index.d.ts.map +1 -0
- package/dist/sitemap-core/index.js +195 -0
- package/dist/sitemap-core/index.js.map +1 -0
- package/dist/social-core/index.d.ts +74 -0
- package/dist/social-core/index.d.ts.map +1 -0
- package/dist/social-core/index.js +72 -0
- package/dist/social-core/index.js.map +1 -0
- package/dist/social.d.ts +7 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +7 -0
- package/dist/social.js.map +1 -0
- package/dist/solid-compat.d.ts +204 -0
- package/dist/solid-compat.d.ts.map +1 -0
- package/dist/solid-compat.js +221 -0
- package/dist/solid-compat.js.map +1 -0
- package/dist/store-core/index.d.ts +141 -0
- package/dist/store-core/index.d.ts.map +1 -0
- package/dist/store-core/index.js +277 -0
- package/dist/store-core/index.js.map +1 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +12 -0
- package/dist/store.js.map +1 -0
- package/dist/vanilla.d.ts +111 -0
- package/dist/vanilla.d.ts.map +1 -1
- package/dist/vanilla.js +206 -0
- package/dist/vanilla.js.map +1 -1
- package/dist/verify-core/index.d.ts +98 -0
- package/dist/verify-core/index.d.ts.map +1 -0
- package/dist/verify-core/index.js +186 -0
- package/dist/verify-core/index.js.map +1 -0
- package/dist/verify.d.ts +8 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +8 -0
- package/dist/verify.js.map +1 -0
- package/openapi.json +4516 -1311
- package/package.json +128 -11
- package/src/account-core/index.ts +309 -0
- package/src/account.ts +11 -0
- package/src/angular/headless.ts +519 -0
- package/src/angular/index.ts +3 -0
- package/src/auth-links.ts +76 -0
- package/src/blog-core/index.ts +423 -0
- package/src/blog.ts +11 -0
- package/src/chat-core/index.ts +335 -0
- package/src/chat.ts +11 -0
- package/src/checkout-core/index.ts +225 -0
- package/src/checkout.ts +10 -0
- package/src/cli.ts +184 -28
- package/src/client.ts +1097 -0
- package/src/consent-core/index.ts +213 -0
- package/src/contracts.ts +768 -11
- package/src/data-model-schema.ts +1 -1
- package/src/element.ts +102 -0
- package/src/embed-protocol.ts +100 -0
- package/src/framework-support.ts +440 -0
- package/src/legal-core/index.ts +276 -0
- package/src/marketing-core/index.ts +209 -0
- package/src/marketing.ts +7 -0
- package/src/mcp-adapters/index.ts +264 -0
- package/src/mcp-adapters.ts +7 -0
- package/src/model3d-core/index.ts +248 -0
- package/src/notifications-core/index.ts +650 -0
- package/src/portal-core/index.ts +1564 -0
- package/src/portal.ts +7 -0
- package/src/push-core/index.ts +335 -0
- package/src/qwik.tsx +602 -2
- package/src/react-marketing.tsx +5 -0
- package/src/react.tsx +434 -319
- package/src/seed-core/index.ts +735 -0
- package/src/seed.ts +7 -0
- package/src/seo-core/blog.ts +216 -0
- package/src/seo-core/commerce.ts +138 -0
- package/src/seo-core/index.ts +605 -0
- package/src/seo-core/local.ts +333 -0
- package/src/sitemap-core/index.ts +296 -0
- package/src/social-core/index.ts +103 -0
- package/src/social.ts +7 -0
- package/src/solid-compat.tsx +347 -0
- package/src/solid-surfaces.tsx +1311 -0
- package/src/solid.tsx +82 -60
- package/src/store-core/index.ts +426 -0
- package/src/store.ts +12 -0
- package/src/svelte/SocialLinks.svelte +84 -0
- package/src/svelte/headless.ts +435 -0
- package/src/svelte/index.ts +9 -0
- package/src/vanilla.ts +367 -0
- package/src/verify-core/index.ts +288 -0
- package/src/verify.ts +8 -0
- package/src/vue/headless.ts +527 -0
- package/src/vue/index.ts +3 -0
package/src/vanilla.ts
CHANGED
|
@@ -1288,3 +1288,370 @@ function observeReveal(node: HTMLElement): void {
|
|
|
1288
1288
|
);
|
|
1289
1289
|
obs.observe(node);
|
|
1290
1290
|
}
|
|
1291
|
+
|
|
1292
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1293
|
+
// Native storefront + chat, framework-free
|
|
1294
|
+
//
|
|
1295
|
+
// There is no reactivity system here to bridge into, so the controllers ARE the
|
|
1296
|
+
// binding: subscribe, render, call the actions. That is also why these are the
|
|
1297
|
+
// reference implementations — every framework binding is a wrapper around
|
|
1298
|
+
// exactly these objects.
|
|
1299
|
+
//
|
|
1300
|
+
// const store = createStorefront({ apiKey, baseUrl });
|
|
1301
|
+
// store.subscribe((s) => render(s.products));
|
|
1302
|
+
// await store.load();
|
|
1303
|
+
//
|
|
1304
|
+
// Remember to `destroy()` when the surface goes away.
|
|
1305
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1306
|
+
|
|
1307
|
+
import {
|
|
1308
|
+
type ChatController,
|
|
1309
|
+
createChatController,
|
|
1310
|
+
} from "./chat-core/index.js";
|
|
1311
|
+
import { BiabDevClient as BiabDevClientForSurfaces } from "./client.js";
|
|
1312
|
+
import {
|
|
1313
|
+
createStoreController,
|
|
1314
|
+
type StoreController,
|
|
1315
|
+
} from "./store-core/index.js";
|
|
1316
|
+
|
|
1317
|
+
export type CreateStorefrontOptions = {
|
|
1318
|
+
apiKey: string;
|
|
1319
|
+
baseUrl: string;
|
|
1320
|
+
pageSize?: number;
|
|
1321
|
+
categoryId?: string | null;
|
|
1322
|
+
onError?: (error: Error) => void;
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
/** Headless storefront + cart. Call `load()` to populate it. */
|
|
1326
|
+
export function createStorefront(
|
|
1327
|
+
options: CreateStorefrontOptions,
|
|
1328
|
+
): StoreController {
|
|
1329
|
+
return createStoreController({
|
|
1330
|
+
client: new BiabDevClientForSurfaces({
|
|
1331
|
+
apiKey: options.apiKey,
|
|
1332
|
+
baseUrl: options.baseUrl,
|
|
1333
|
+
}),
|
|
1334
|
+
...(options.pageSize ? { pageSize: options.pageSize } : {}),
|
|
1335
|
+
...(options.categoryId ? { categoryId: options.categoryId } : {}),
|
|
1336
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
export type CreateChatOptions = {
|
|
1341
|
+
apiKey: string;
|
|
1342
|
+
baseUrl: string;
|
|
1343
|
+
onError?: (error: Error) => void;
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
/** Headless Front Desk chat — the native alternative to the iframe widget. */
|
|
1347
|
+
export function createChat(options: CreateChatOptions): ChatController {
|
|
1348
|
+
return createChatController({
|
|
1349
|
+
client: new BiabDevClientForSurfaces({
|
|
1350
|
+
apiKey: options.apiKey,
|
|
1351
|
+
baseUrl: options.baseUrl,
|
|
1352
|
+
}),
|
|
1353
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
export type { ChatController, ChatSnapshot } from "./chat-core/index.js";
|
|
1358
|
+
export type {
|
|
1359
|
+
StoreController,
|
|
1360
|
+
StoreSnapshot,
|
|
1361
|
+
} from "./store-core/index.js";
|
|
1362
|
+
|
|
1363
|
+
import {
|
|
1364
|
+
type BlogController,
|
|
1365
|
+
createBlogController,
|
|
1366
|
+
} from "./blog-core/index.js";
|
|
1367
|
+
|
|
1368
|
+
export type CreateBlogOptions = {
|
|
1369
|
+
apiKey: string;
|
|
1370
|
+
baseUrl: string;
|
|
1371
|
+
pageSize?: number;
|
|
1372
|
+
commentPageSize?: number;
|
|
1373
|
+
categoryId?: string | null;
|
|
1374
|
+
onError?: (error: Error) => void;
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
/** Headless blog — posts, taxonomy, a single post, comments and likes. */
|
|
1378
|
+
export function createBlog(options: CreateBlogOptions): BlogController {
|
|
1379
|
+
return createBlogController({
|
|
1380
|
+
client: new BiabDevClientForSurfaces({
|
|
1381
|
+
apiKey: options.apiKey,
|
|
1382
|
+
baseUrl: options.baseUrl,
|
|
1383
|
+
}),
|
|
1384
|
+
...(options.pageSize ? { pageSize: options.pageSize } : {}),
|
|
1385
|
+
...(options.commentPageSize
|
|
1386
|
+
? { commentPageSize: options.commentPageSize }
|
|
1387
|
+
: {}),
|
|
1388
|
+
...(options.categoryId ? { categoryId: options.categoryId } : {}),
|
|
1389
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
export type { BlogController, BlogSnapshot } from "./blog-core/index.js";
|
|
1394
|
+
|
|
1395
|
+
import {
|
|
1396
|
+
createFollowersController,
|
|
1397
|
+
createUserController,
|
|
1398
|
+
type FollowersController,
|
|
1399
|
+
type UserController,
|
|
1400
|
+
} from "./account-core/index.js";
|
|
1401
|
+
import {
|
|
1402
|
+
type CheckoutController,
|
|
1403
|
+
createCheckoutController,
|
|
1404
|
+
} from "./checkout-core/index.js";
|
|
1405
|
+
import { createBiabClient as createBiabClientForFollowers } from "./sdk.js";
|
|
1406
|
+
|
|
1407
|
+
/** Headless checkout — mint a Stripe session, then `redirect()` or render it. */
|
|
1408
|
+
export function createCheckout(options: {
|
|
1409
|
+
apiKey: string;
|
|
1410
|
+
baseUrl: string;
|
|
1411
|
+
onError?: (error: Error) => void;
|
|
1412
|
+
}): CheckoutController {
|
|
1413
|
+
return createCheckoutController({
|
|
1414
|
+
client: new BiabDevClientForSurfaces({
|
|
1415
|
+
apiKey: options.apiKey,
|
|
1416
|
+
baseUrl: options.baseUrl,
|
|
1417
|
+
}),
|
|
1418
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/** The signed-in customer, read from the auth handler's `/me`. */
|
|
1423
|
+
export function createUser(
|
|
1424
|
+
options: { basePath?: string } = {},
|
|
1425
|
+
): UserController {
|
|
1426
|
+
return createUserController(options);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/** Newsletter / follower subscribe, bound to a publishable token. */
|
|
1430
|
+
export function createFollowers(options: {
|
|
1431
|
+
token: string;
|
|
1432
|
+
siteId: string;
|
|
1433
|
+
baseUrl?: string;
|
|
1434
|
+
onError?: (error: Error) => void;
|
|
1435
|
+
}): FollowersController {
|
|
1436
|
+
// Followers hang off the PUBLISHABLE client (`createBiabClient`), not
|
|
1437
|
+
// `BiabDevClient`: they are the one surface a browser calls directly with an
|
|
1438
|
+
// origin-locked `pk_…` token and no backend.
|
|
1439
|
+
return createFollowersController({
|
|
1440
|
+
client: createBiabClientForFollowers({
|
|
1441
|
+
apiKey: options.token,
|
|
1442
|
+
siteId: options.siteId,
|
|
1443
|
+
baseUrl: options.baseUrl ?? DEFAULT_PACKAGE_BASE_URL_FOR_SURFACES,
|
|
1444
|
+
}),
|
|
1445
|
+
siteId: options.siteId,
|
|
1446
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
export type {
|
|
1451
|
+
FollowersController,
|
|
1452
|
+
FollowersSnapshot,
|
|
1453
|
+
UserController,
|
|
1454
|
+
UserSnapshot,
|
|
1455
|
+
} from "./account-core/index.js";
|
|
1456
|
+
export {
|
|
1457
|
+
signIn,
|
|
1458
|
+
signInHref,
|
|
1459
|
+
signOut,
|
|
1460
|
+
signOutHref,
|
|
1461
|
+
signUp,
|
|
1462
|
+
signUpHref,
|
|
1463
|
+
} from "./account-core/index.js";
|
|
1464
|
+
export type {
|
|
1465
|
+
CheckoutController,
|
|
1466
|
+
CheckoutSnapshot,
|
|
1467
|
+
} from "./checkout-core/index.js";
|
|
1468
|
+
|
|
1469
|
+
/** Production package-API base, used when a surface doesn't specify one. */
|
|
1470
|
+
const DEFAULT_PACKAGE_BASE_URL_FOR_SURFACES =
|
|
1471
|
+
"https://businessdash.us/api/package/v1";
|
|
1472
|
+
|
|
1473
|
+
import {
|
|
1474
|
+
createMarketingController,
|
|
1475
|
+
type MarketingController,
|
|
1476
|
+
} from "./marketing-core/index.js";
|
|
1477
|
+
import {
|
|
1478
|
+
type CreateVerifyControllerOptions,
|
|
1479
|
+
createVerifyController,
|
|
1480
|
+
type VerifyController,
|
|
1481
|
+
} from "./verify-core/index.js";
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Headless marketing pages — the index, plus one page at a time.
|
|
1485
|
+
*
|
|
1486
|
+
* Bound to the PUBLISHABLE client, like followers: marketing pages are a
|
|
1487
|
+
* browser-readable surface keyed by site, so they need a `siteId` and work with
|
|
1488
|
+
* an origin-locked `pk_…` token.
|
|
1489
|
+
*/
|
|
1490
|
+
export function createMarketing(options: {
|
|
1491
|
+
token: string;
|
|
1492
|
+
siteId: string;
|
|
1493
|
+
baseUrl?: string;
|
|
1494
|
+
onError?: (error: Error) => void;
|
|
1495
|
+
}): MarketingController {
|
|
1496
|
+
return createMarketingController({
|
|
1497
|
+
client: createBiabClientForFollowers({
|
|
1498
|
+
apiKey: options.token,
|
|
1499
|
+
siteId: options.siteId,
|
|
1500
|
+
baseUrl: options.baseUrl ?? DEFAULT_PACKAGE_BASE_URL_FOR_SURFACES,
|
|
1501
|
+
}),
|
|
1502
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/** Headless email / phone verification. Read `flow` before rendering a field. */
|
|
1507
|
+
export function createVerify(
|
|
1508
|
+
options: {
|
|
1509
|
+
apiKey: string;
|
|
1510
|
+
baseUrl: string;
|
|
1511
|
+
} & Omit<CreateVerifyControllerOptions, "client">,
|
|
1512
|
+
): VerifyController {
|
|
1513
|
+
const { apiKey, baseUrl, ...rest } = options;
|
|
1514
|
+
return createVerifyController({
|
|
1515
|
+
client: new BiabDevClientForSurfaces({ apiKey, baseUrl }),
|
|
1516
|
+
...rest,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
export type {
|
|
1521
|
+
MarketingController,
|
|
1522
|
+
MarketingSnapshot,
|
|
1523
|
+
} from "./marketing-core/index.js";
|
|
1524
|
+
export type {
|
|
1525
|
+
VerifyController,
|
|
1526
|
+
VerifyFlow,
|
|
1527
|
+
VerifySnapshot,
|
|
1528
|
+
} from "./verify-core/index.js";
|
|
1529
|
+
|
|
1530
|
+
import {
|
|
1531
|
+
createPortalController,
|
|
1532
|
+
type PortalController,
|
|
1533
|
+
} from "./portal-core/index.js";
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* The signed-in customer's portal. `sessionToken` is required — every portal
|
|
1537
|
+
* route is scoped to the customer, and without one it renders empty rather than
|
|
1538
|
+
* signed-out.
|
|
1539
|
+
*/
|
|
1540
|
+
export function createPortal(options: {
|
|
1541
|
+
apiKey: string;
|
|
1542
|
+
baseUrl: string;
|
|
1543
|
+
sessionToken: string;
|
|
1544
|
+
onError?: (error: Error) => void;
|
|
1545
|
+
}): PortalController {
|
|
1546
|
+
return createPortalController({
|
|
1547
|
+
client: new BiabDevClientForSurfaces({
|
|
1548
|
+
apiKey: options.apiKey,
|
|
1549
|
+
baseUrl: options.baseUrl,
|
|
1550
|
+
}),
|
|
1551
|
+
sessionToken: options.sessionToken,
|
|
1552
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
export type { PortalController, PortalSnapshot } from "./portal-core/index.js";
|
|
1557
|
+
|
|
1558
|
+
import {
|
|
1559
|
+
resolveSocialLinks,
|
|
1560
|
+
SOCIAL_FALLBACK_GLYPH,
|
|
1561
|
+
SOCIAL_LIST_STYLE,
|
|
1562
|
+
type SocialLinkRow,
|
|
1563
|
+
} from "./social-core/index.js";
|
|
1564
|
+
|
|
1565
|
+
export type MountSocialLinksOptions = {
|
|
1566
|
+
socials: unknown;
|
|
1567
|
+
/** Icon colour — hex without `#`, or a CSS keyword. */
|
|
1568
|
+
color?: string;
|
|
1569
|
+
/** Icon edge length in px. Default 20. */
|
|
1570
|
+
size?: number;
|
|
1571
|
+
class?: string;
|
|
1572
|
+
linkClass?: string;
|
|
1573
|
+
iconClass?: string;
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
/**
|
|
1577
|
+
* Render a company's social links into `target`, replacing its contents.
|
|
1578
|
+
*
|
|
1579
|
+
* Returns a `destroy()` that empties the element again, so a single-page app
|
|
1580
|
+
* can tear it down. Renders nothing at all when there are no profiles — an
|
|
1581
|
+
* empty `<ul>` in a footer is worse than no list.
|
|
1582
|
+
*/
|
|
1583
|
+
export function mountSocialLinks(
|
|
1584
|
+
target: Element,
|
|
1585
|
+
options: MountSocialLinksOptions,
|
|
1586
|
+
): { destroy: () => void } {
|
|
1587
|
+
const size = options.size ?? 20;
|
|
1588
|
+
const rows = resolveSocialLinks(options.socials, { color: options.color });
|
|
1589
|
+
target.replaceChildren();
|
|
1590
|
+
if (rows.length === 0) return { destroy: () => target.replaceChildren() };
|
|
1591
|
+
|
|
1592
|
+
const ul = document.createElement("ul");
|
|
1593
|
+
if (options.class) ul.className = options.class;
|
|
1594
|
+
for (const [prop, value] of Object.entries(SOCIAL_LIST_STYLE)) {
|
|
1595
|
+
ul.style.setProperty(prop, value);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
for (const row of rows) {
|
|
1599
|
+
const li = document.createElement("li");
|
|
1600
|
+
const a = document.createElement("a");
|
|
1601
|
+
a.href = row.url;
|
|
1602
|
+
a.target = "_blank";
|
|
1603
|
+
a.rel = "noopener noreferrer";
|
|
1604
|
+
a.title = row.label;
|
|
1605
|
+
a.setAttribute("aria-label", row.label);
|
|
1606
|
+
if (options.linkClass) a.className = options.linkClass;
|
|
1607
|
+
a.append(socialGlyph(row, size, options.iconClass));
|
|
1608
|
+
li.append(a);
|
|
1609
|
+
ul.append(li);
|
|
1610
|
+
}
|
|
1611
|
+
target.append(ul);
|
|
1612
|
+
|
|
1613
|
+
return { destroy: () => target.replaceChildren() };
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/** Brand icon when the platform has one, generic globe when it doesn't. */
|
|
1617
|
+
function socialGlyph(
|
|
1618
|
+
row: SocialLinkRow,
|
|
1619
|
+
size: number,
|
|
1620
|
+
iconClass?: string,
|
|
1621
|
+
): Element {
|
|
1622
|
+
if (row.iconUrl) {
|
|
1623
|
+
const img = document.createElement("img");
|
|
1624
|
+
img.src = row.iconUrl;
|
|
1625
|
+
img.alt = row.label;
|
|
1626
|
+
img.width = size;
|
|
1627
|
+
img.height = size;
|
|
1628
|
+
img.loading = "lazy";
|
|
1629
|
+
if (iconClass) img.className = iconClass;
|
|
1630
|
+
return img;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
1634
|
+
const svg = document.createElementNS(ns, "svg");
|
|
1635
|
+
svg.setAttribute("viewBox", SOCIAL_FALLBACK_GLYPH.viewBox);
|
|
1636
|
+
svg.setAttribute("width", String(size));
|
|
1637
|
+
svg.setAttribute("height", String(size));
|
|
1638
|
+
svg.setAttribute("fill", "none");
|
|
1639
|
+
svg.setAttribute("stroke", "currentColor");
|
|
1640
|
+
svg.setAttribute("stroke-width", String(SOCIAL_FALLBACK_GLYPH.strokeWidth));
|
|
1641
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1642
|
+
if (iconClass) svg.setAttribute("class", iconClass);
|
|
1643
|
+
|
|
1644
|
+
const title = document.createElementNS(ns, "title");
|
|
1645
|
+
title.textContent = row.label;
|
|
1646
|
+
const circle = document.createElementNS(ns, "circle");
|
|
1647
|
+
circle.setAttribute("cx", String(SOCIAL_FALLBACK_GLYPH.circle.cx));
|
|
1648
|
+
circle.setAttribute("cy", String(SOCIAL_FALLBACK_GLYPH.circle.cy));
|
|
1649
|
+
circle.setAttribute("r", String(SOCIAL_FALLBACK_GLYPH.circle.r));
|
|
1650
|
+
const path = document.createElementNS(ns, "path");
|
|
1651
|
+
path.setAttribute("d", SOCIAL_FALLBACK_GLYPH.path);
|
|
1652
|
+
svg.append(title, circle, path);
|
|
1653
|
+
return svg;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
export type { SocialLinkRow } from "./social-core/index.js";
|
|
1657
|
+
export { resolveSocialLinks } from "./social-core/index.js";
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@businessdash/sdk/verify` — the headless email / phone verification flow.
|
|
3
|
+
*
|
|
4
|
+
* Both flows were React-only, as ~500 lines of multi-step component. The state
|
|
5
|
+
* machine underneath is small and identical for email and phone: ask for a
|
|
6
|
+
* token, wait, confirm it, handle it being wrong.
|
|
7
|
+
*
|
|
8
|
+
* Note `flow` on the snapshot. The server decides per request whether to send a
|
|
9
|
+
* one-time code or an emailed link, so a binding must read it rather than
|
|
10
|
+
* assume a code field is always wanted.
|
|
11
|
+
*
|
|
12
|
+
* This is that machine. The markup stays yours, which matters more here than
|
|
13
|
+
* elsewhere — a verification step sits inside a signup or a checkout, and an
|
|
14
|
+
* SDK-shaped panel in the middle of your own flow is jarring.
|
|
15
|
+
*
|
|
16
|
+
* ## Resend cooldown
|
|
17
|
+
*
|
|
18
|
+
* `resendAvailableAt` is a timestamp, not a ticking counter. A controller that
|
|
19
|
+
* ran its own interval would keep a timer alive for every mounted form and
|
|
20
|
+
* still be wrong after a tab sleeps. The binding renders the remaining seconds
|
|
21
|
+
* from the timestamp however its framework prefers, and `canResend()` answers
|
|
22
|
+
* the only question that actually gates the button.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
ConfirmVerificationInput,
|
|
29
|
+
ConfirmVerificationResponse,
|
|
30
|
+
StartVerificationInput,
|
|
31
|
+
StartVerificationResponse,
|
|
32
|
+
} from "../contracts.js";
|
|
33
|
+
|
|
34
|
+
export type VerifyCoreClient = {
|
|
35
|
+
notifications: {
|
|
36
|
+
startVerification(
|
|
37
|
+
input: StartVerificationInput,
|
|
38
|
+
): Promise<StartVerificationResponse>;
|
|
39
|
+
confirmVerification(
|
|
40
|
+
input: ConfirmVerificationInput,
|
|
41
|
+
): Promise<ConfirmVerificationResponse>;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* How the platform chose to verify this destination.
|
|
47
|
+
*
|
|
48
|
+
* `otp` means a code the visitor types. `link` means an email they click, and
|
|
49
|
+
* there is NO code field to render — the page just waits. The server decides
|
|
50
|
+
* per request, so a binding must read `flow` rather than assume, or half its
|
|
51
|
+
* users stare at a box waiting for a code that is never coming.
|
|
52
|
+
*/
|
|
53
|
+
export type VerifyFlow = "link" | "otp";
|
|
54
|
+
|
|
55
|
+
export type VerifyStep =
|
|
56
|
+
/** Nothing requested yet — show the address/number field. */
|
|
57
|
+
| "collecting"
|
|
58
|
+
/** A code has been sent — show the code field. */
|
|
59
|
+
| "awaiting-code"
|
|
60
|
+
/** Confirmed. */
|
|
61
|
+
| "verified";
|
|
62
|
+
|
|
63
|
+
/** Field names are STABLE and load-bearing for every framework binding. */
|
|
64
|
+
export type VerifySnapshot = {
|
|
65
|
+
step: VerifyStep;
|
|
66
|
+
/** The address or number a code was sent to. */
|
|
67
|
+
destination: string | null;
|
|
68
|
+
isPending: boolean;
|
|
69
|
+
/** The last failure — typically a wrong or expired code. */
|
|
70
|
+
error: Error | null;
|
|
71
|
+
/**
|
|
72
|
+
* Epoch ms when a resend becomes allowed, or `null` when none is pending.
|
|
73
|
+
* Render the countdown yourself; see the note above.
|
|
74
|
+
*/
|
|
75
|
+
resendAvailableAt: number | null;
|
|
76
|
+
/** `otp` (type a code) or `link` (click an email). `null` before starting. */
|
|
77
|
+
flow: VerifyFlow | null;
|
|
78
|
+
/** The server's own wording for what it just sent. Worth showing verbatim. */
|
|
79
|
+
message: string | null;
|
|
80
|
+
/** How many codes have been sent this session. */
|
|
81
|
+
attempts: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type VerifyController = {
|
|
85
|
+
snapshot(): VerifySnapshot;
|
|
86
|
+
subscribe(callback: (snapshot: VerifySnapshot) => void): () => void;
|
|
87
|
+
/** Send a code to an address or number. Moves to `awaiting-code`. */
|
|
88
|
+
start(destination: string): Promise<boolean>;
|
|
89
|
+
/** Send another code. No-ops while the cooldown is active. */
|
|
90
|
+
resend(): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* Submit the token from the code field or the emailed link. Moves to
|
|
93
|
+
* `verified` on success; stays on `awaiting-code` when it is rejected.
|
|
94
|
+
*/
|
|
95
|
+
confirm(token: string): Promise<boolean>;
|
|
96
|
+
/** True when a resend is allowed right now. */
|
|
97
|
+
canResend(now?: number): boolean;
|
|
98
|
+
/** Back to `collecting`, e.g. the visitor mistyped their address. */
|
|
99
|
+
reset(): void;
|
|
100
|
+
destroy(): void;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type CreateVerifyControllerOptions = {
|
|
104
|
+
client: VerifyCoreClient;
|
|
105
|
+
/** `email` or `phone` — passed straight through to the API. */
|
|
106
|
+
kind: StartVerificationInput["kind"];
|
|
107
|
+
/** Seconds before a resend is allowed. Default 30. */
|
|
108
|
+
resendCooldownSeconds?: number;
|
|
109
|
+
onError?: (error: Error) => void;
|
|
110
|
+
/** Called once the code is accepted. */
|
|
111
|
+
onVerified?: (destination: string) => void;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const DEFAULT_COOLDOWN_SECONDS = 30;
|
|
115
|
+
|
|
116
|
+
function toError(cause: unknown, fallback: string): Error {
|
|
117
|
+
return cause instanceof Error ? cause : new Error(fallback);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createVerifyController(
|
|
121
|
+
options: CreateVerifyControllerOptions,
|
|
122
|
+
): VerifyController {
|
|
123
|
+
const { client, kind, onError, onVerified } = options;
|
|
124
|
+
const cooldownMs =
|
|
125
|
+
(options.resendCooldownSeconds ?? DEFAULT_COOLDOWN_SECONDS) * 1000;
|
|
126
|
+
|
|
127
|
+
let destroyed = false;
|
|
128
|
+
const subscribers = new Set<(s: VerifySnapshot) => void>();
|
|
129
|
+
|
|
130
|
+
const state: {
|
|
131
|
+
step: VerifyStep;
|
|
132
|
+
destination: string | null;
|
|
133
|
+
isPending: boolean;
|
|
134
|
+
error: Error | null;
|
|
135
|
+
resendAvailableAt: number | null;
|
|
136
|
+
attempts: number;
|
|
137
|
+
flow: VerifyFlow | null;
|
|
138
|
+
message: string | null;
|
|
139
|
+
} = {
|
|
140
|
+
step: "collecting",
|
|
141
|
+
destination: null,
|
|
142
|
+
isPending: false,
|
|
143
|
+
error: null,
|
|
144
|
+
resendAvailableAt: null,
|
|
145
|
+
attempts: 0,
|
|
146
|
+
flow: null,
|
|
147
|
+
message: null,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
let cached: VerifySnapshot | null = null;
|
|
151
|
+
|
|
152
|
+
function snapshot(): VerifySnapshot {
|
|
153
|
+
if (!cached) {
|
|
154
|
+
cached = {
|
|
155
|
+
step: state.step,
|
|
156
|
+
destination: state.destination,
|
|
157
|
+
isPending: state.isPending,
|
|
158
|
+
error: state.error,
|
|
159
|
+
resendAvailableAt: state.resendAvailableAt,
|
|
160
|
+
attempts: state.attempts,
|
|
161
|
+
flow: state.flow,
|
|
162
|
+
message: state.message,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return cached;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function notify(): void {
|
|
169
|
+
if (destroyed) return;
|
|
170
|
+
cached = null;
|
|
171
|
+
const next = snapshot();
|
|
172
|
+
for (const cb of subscribers) cb(next);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function fail(cause: unknown, fallback: string): void {
|
|
176
|
+
const error = toError(cause, fallback);
|
|
177
|
+
state.error = error;
|
|
178
|
+
onError?.(error);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function canResend(now = Date.now()): boolean {
|
|
182
|
+
if (state.step !== "awaiting-code" || state.isPending) return false;
|
|
183
|
+
return state.resendAvailableAt === null || now >= state.resendAvailableAt;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function send(destination: string): Promise<boolean> {
|
|
187
|
+
state.isPending = true;
|
|
188
|
+
state.error = null;
|
|
189
|
+
notify();
|
|
190
|
+
try {
|
|
191
|
+
const res = await client.notifications.startVerification({
|
|
192
|
+
kind,
|
|
193
|
+
destination,
|
|
194
|
+
});
|
|
195
|
+
if (destroyed) return false;
|
|
196
|
+
state.flow = res.flow;
|
|
197
|
+
state.message = res.message;
|
|
198
|
+
state.destination = destination;
|
|
199
|
+
state.step = "awaiting-code";
|
|
200
|
+
state.attempts += 1;
|
|
201
|
+
state.resendAvailableAt = Date.now() + cooldownMs;
|
|
202
|
+
return true;
|
|
203
|
+
} catch (cause) {
|
|
204
|
+
if (destroyed) return false;
|
|
205
|
+
fail(cause, "Could not send the code.");
|
|
206
|
+
return false;
|
|
207
|
+
} finally {
|
|
208
|
+
if (!destroyed) {
|
|
209
|
+
state.isPending = false;
|
|
210
|
+
notify();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
snapshot,
|
|
217
|
+
|
|
218
|
+
subscribe(callback) {
|
|
219
|
+
subscribers.add(callback);
|
|
220
|
+
return () => {
|
|
221
|
+
subscribers.delete(callback);
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
start(destination) {
|
|
226
|
+
if (destroyed) return Promise.resolve(false);
|
|
227
|
+
return send(destination);
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
async resend() {
|
|
231
|
+
// Guarded here rather than left to the binding: otherwise every
|
|
232
|
+
// framework reimplements the cooldown, and the one that gets it wrong
|
|
233
|
+
// lets a visitor spam themselves with texts.
|
|
234
|
+
if (destroyed || !canResend() || !state.destination) return false;
|
|
235
|
+
return send(state.destination);
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async confirm(token) {
|
|
239
|
+
if (destroyed || !state.destination) return false;
|
|
240
|
+
state.isPending = true;
|
|
241
|
+
state.error = null;
|
|
242
|
+
notify();
|
|
243
|
+
try {
|
|
244
|
+
const res = await client.notifications.confirmVerification({ token });
|
|
245
|
+
if (destroyed) return false;
|
|
246
|
+
state.step = "verified";
|
|
247
|
+
// The response carries the destination the server actually verified,
|
|
248
|
+
// which is the authoritative one — a visitor can start a flow, edit
|
|
249
|
+
// the field, and confirm a token minted for the earlier value.
|
|
250
|
+
state.destination = res.destination;
|
|
251
|
+
onVerified?.(res.destination);
|
|
252
|
+
return true;
|
|
253
|
+
} catch (cause) {
|
|
254
|
+
if (destroyed) return false;
|
|
255
|
+
// The endpoint answers `{ ok: true }` or rejects, so a rejection here
|
|
256
|
+
// is the ordinary wrong-or-expired-code path, not an outage. It is
|
|
257
|
+
// reported as an error to render, and the step stays `awaiting-code`
|
|
258
|
+
// so the visitor can try again.
|
|
259
|
+
fail(cause, "That code didn't work. It may have expired.");
|
|
260
|
+
return false;
|
|
261
|
+
} finally {
|
|
262
|
+
if (!destroyed) {
|
|
263
|
+
state.isPending = false;
|
|
264
|
+
notify();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
canResend,
|
|
270
|
+
|
|
271
|
+
reset() {
|
|
272
|
+
if (destroyed) return;
|
|
273
|
+
state.step = "collecting";
|
|
274
|
+
state.destination = null;
|
|
275
|
+
state.error = null;
|
|
276
|
+
state.resendAvailableAt = null;
|
|
277
|
+
state.isPending = false;
|
|
278
|
+
state.flow = null;
|
|
279
|
+
state.message = null;
|
|
280
|
+
notify();
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
destroy() {
|
|
284
|
+
destroyed = true;
|
|
285
|
+
subscribers.clear();
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|