@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.
Files changed (239) hide show
  1. package/README.md +991 -295
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +622 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +888 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/consent-core/index.cjs +107 -0
  47. package/dist/consent-core/index.d.ts +103 -0
  48. package/dist/consent-core/index.d.ts.map +1 -0
  49. package/dist/consent-core/index.js +157 -0
  50. package/dist/consent-core/index.js.map +1 -0
  51. package/dist/contracts.d.ts +3526 -767
  52. package/dist/contracts.d.ts.map +1 -1
  53. package/dist/contracts.js +571 -4
  54. package/dist/contracts.js.map +1 -1
  55. package/dist/data-model-schema.d.ts +1 -1
  56. package/dist/data-model-schema.d.ts.map +1 -1
  57. package/dist/element.d.ts +30 -0
  58. package/dist/element.d.ts.map +1 -1
  59. package/dist/element.js +56 -0
  60. package/dist/element.js.map +1 -1
  61. package/dist/embed-protocol.d.ts +86 -0
  62. package/dist/embed-protocol.d.ts.map +1 -0
  63. package/dist/embed-protocol.js +60 -0
  64. package/dist/embed-protocol.js.map +1 -0
  65. package/dist/framework-support.d.ts +122 -0
  66. package/dist/framework-support.d.ts.map +1 -0
  67. package/dist/framework-support.js +323 -0
  68. package/dist/framework-support.js.map +1 -0
  69. package/dist/index.cjs +1355 -24
  70. package/dist/legal-core/index.cjs +115 -0
  71. package/dist/legal-core/index.d.ts +160 -0
  72. package/dist/legal-core/index.d.ts.map +1 -0
  73. package/dist/legal-core/index.js +220 -0
  74. package/dist/legal-core/index.js.map +1 -0
  75. package/dist/marketing-core/index.d.ts +66 -0
  76. package/dist/marketing-core/index.d.ts.map +1 -0
  77. package/dist/marketing-core/index.js +154 -0
  78. package/dist/marketing-core/index.js.map +1 -0
  79. package/dist/marketing.d.ts +7 -0
  80. package/dist/marketing.d.ts.map +1 -0
  81. package/dist/marketing.js +7 -0
  82. package/dist/marketing.js.map +1 -0
  83. package/dist/mcp-adapters/index.d.ts +135 -0
  84. package/dist/mcp-adapters/index.d.ts.map +1 -0
  85. package/dist/mcp-adapters/index.js +187 -0
  86. package/dist/mcp-adapters/index.js.map +1 -0
  87. package/dist/mcp-adapters.d.ts +7 -0
  88. package/dist/mcp-adapters.d.ts.map +1 -0
  89. package/dist/mcp-adapters.js +7 -0
  90. package/dist/mcp-adapters.js.map +1 -0
  91. package/dist/model3d-core/index.cjs +95 -0
  92. package/dist/model3d-core/index.d.ts +167 -0
  93. package/dist/model3d-core/index.d.ts.map +1 -0
  94. package/dist/model3d-core/index.js +158 -0
  95. package/dist/model3d-core/index.js.map +1 -0
  96. package/dist/notifications-core/index.cjs +315 -0
  97. package/dist/notifications-core/index.d.ts +229 -0
  98. package/dist/notifications-core/index.d.ts.map +1 -0
  99. package/dist/notifications-core/index.js +445 -0
  100. package/dist/notifications-core/index.js.map +1 -0
  101. package/dist/portal-core/index.d.ts +451 -0
  102. package/dist/portal-core/index.d.ts.map +1 -0
  103. package/dist/portal-core/index.js +819 -0
  104. package/dist/portal-core/index.js.map +1 -0
  105. package/dist/portal.d.ts +7 -0
  106. package/dist/portal.d.ts.map +1 -0
  107. package/dist/portal.js +7 -0
  108. package/dist/portal.js.map +1 -0
  109. package/dist/push-core/index.cjs +171 -0
  110. package/dist/push-core/index.d.ts +149 -0
  111. package/dist/push-core/index.d.ts.map +1 -0
  112. package/dist/push-core/index.js +231 -0
  113. package/dist/push-core/index.js.map +1 -0
  114. package/dist/react-marketing.d.ts +1 -17
  115. package/dist/react-marketing.d.ts.map +1 -1
  116. package/dist/react-marketing.js +4 -0
  117. package/dist/react-marketing.js.map +1 -1
  118. package/dist/react.d.ts +294 -101
  119. package/dist/react.d.ts.map +1 -1
  120. package/dist/react.js +302 -246
  121. package/dist/react.js.map +1 -1
  122. package/dist/seed-core/index.d.ts +354 -0
  123. package/dist/seed-core/index.d.ts.map +1 -0
  124. package/dist/seed-core/index.js +420 -0
  125. package/dist/seed-core/index.js.map +1 -0
  126. package/dist/seed.d.ts +7 -0
  127. package/dist/seed.d.ts.map +1 -0
  128. package/dist/seed.js +7 -0
  129. package/dist/seed.js.map +1 -0
  130. package/dist/seo-core/blog.d.ts +94 -0
  131. package/dist/seo-core/blog.d.ts.map +1 -0
  132. package/dist/seo-core/blog.js +149 -0
  133. package/dist/seo-core/blog.js.map +1 -0
  134. package/dist/seo-core/commerce.d.ts +64 -0
  135. package/dist/seo-core/commerce.d.ts.map +1 -0
  136. package/dist/seo-core/commerce.js +87 -0
  137. package/dist/seo-core/commerce.js.map +1 -0
  138. package/dist/seo-core/index.cjs +751 -0
  139. package/dist/seo-core/index.d.ts +226 -0
  140. package/dist/seo-core/index.d.ts.map +1 -0
  141. package/dist/seo-core/index.js +455 -0
  142. package/dist/seo-core/index.js.map +1 -0
  143. package/dist/seo-core/local.d.ts +181 -0
  144. package/dist/seo-core/local.d.ts.map +1 -0
  145. package/dist/seo-core/local.js +193 -0
  146. package/dist/seo-core/local.js.map +1 -0
  147. package/dist/sitemap-core/index.cjs +167 -0
  148. package/dist/sitemap-core/index.d.ts +155 -0
  149. package/dist/sitemap-core/index.d.ts.map +1 -0
  150. package/dist/sitemap-core/index.js +195 -0
  151. package/dist/sitemap-core/index.js.map +1 -0
  152. package/dist/social-core/index.d.ts +74 -0
  153. package/dist/social-core/index.d.ts.map +1 -0
  154. package/dist/social-core/index.js +72 -0
  155. package/dist/social-core/index.js.map +1 -0
  156. package/dist/social.d.ts +7 -0
  157. package/dist/social.d.ts.map +1 -0
  158. package/dist/social.js +7 -0
  159. package/dist/social.js.map +1 -0
  160. package/dist/solid-compat.d.ts +204 -0
  161. package/dist/solid-compat.d.ts.map +1 -0
  162. package/dist/solid-compat.js +221 -0
  163. package/dist/solid-compat.js.map +1 -0
  164. package/dist/store-core/index.d.ts +141 -0
  165. package/dist/store-core/index.d.ts.map +1 -0
  166. package/dist/store-core/index.js +277 -0
  167. package/dist/store-core/index.js.map +1 -0
  168. package/dist/store.d.ts +12 -0
  169. package/dist/store.d.ts.map +1 -0
  170. package/dist/store.js +12 -0
  171. package/dist/store.js.map +1 -0
  172. package/dist/vanilla.d.ts +111 -0
  173. package/dist/vanilla.d.ts.map +1 -1
  174. package/dist/vanilla.js +206 -0
  175. package/dist/vanilla.js.map +1 -1
  176. package/dist/verify-core/index.d.ts +98 -0
  177. package/dist/verify-core/index.d.ts.map +1 -0
  178. package/dist/verify-core/index.js +186 -0
  179. package/dist/verify-core/index.js.map +1 -0
  180. package/dist/verify.d.ts +8 -0
  181. package/dist/verify.d.ts.map +1 -0
  182. package/dist/verify.js +8 -0
  183. package/dist/verify.js.map +1 -0
  184. package/openapi.json +4516 -1311
  185. package/package.json +128 -11
  186. package/src/account-core/index.ts +309 -0
  187. package/src/account.ts +11 -0
  188. package/src/angular/headless.ts +519 -0
  189. package/src/angular/index.ts +3 -0
  190. package/src/auth-links.ts +76 -0
  191. package/src/blog-core/index.ts +423 -0
  192. package/src/blog.ts +11 -0
  193. package/src/chat-core/index.ts +335 -0
  194. package/src/chat.ts +11 -0
  195. package/src/checkout-core/index.ts +225 -0
  196. package/src/checkout.ts +10 -0
  197. package/src/cli.ts +184 -28
  198. package/src/client.ts +1097 -0
  199. package/src/consent-core/index.ts +213 -0
  200. package/src/contracts.ts +768 -11
  201. package/src/data-model-schema.ts +1 -1
  202. package/src/element.ts +102 -0
  203. package/src/embed-protocol.ts +100 -0
  204. package/src/framework-support.ts +440 -0
  205. package/src/legal-core/index.ts +276 -0
  206. package/src/marketing-core/index.ts +209 -0
  207. package/src/marketing.ts +7 -0
  208. package/src/mcp-adapters/index.ts +264 -0
  209. package/src/mcp-adapters.ts +7 -0
  210. package/src/model3d-core/index.ts +248 -0
  211. package/src/notifications-core/index.ts +650 -0
  212. package/src/portal-core/index.ts +1564 -0
  213. package/src/portal.ts +7 -0
  214. package/src/push-core/index.ts +335 -0
  215. package/src/qwik.tsx +602 -2
  216. package/src/react-marketing.tsx +5 -0
  217. package/src/react.tsx +434 -319
  218. package/src/seed-core/index.ts +735 -0
  219. package/src/seed.ts +7 -0
  220. package/src/seo-core/blog.ts +216 -0
  221. package/src/seo-core/commerce.ts +138 -0
  222. package/src/seo-core/index.ts +605 -0
  223. package/src/seo-core/local.ts +333 -0
  224. package/src/sitemap-core/index.ts +296 -0
  225. package/src/social-core/index.ts +103 -0
  226. package/src/social.ts +7 -0
  227. package/src/solid-compat.tsx +347 -0
  228. package/src/solid-surfaces.tsx +1311 -0
  229. package/src/solid.tsx +82 -60
  230. package/src/store-core/index.ts +426 -0
  231. package/src/store.ts +12 -0
  232. package/src/svelte/SocialLinks.svelte +84 -0
  233. package/src/svelte/headless.ts +435 -0
  234. package/src/svelte/index.ts +9 -0
  235. package/src/vanilla.ts +367 -0
  236. package/src/verify-core/index.ts +288 -0
  237. package/src/verify.ts +8 -0
  238. package/src/vue/headless.ts +527 -0
  239. 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
+ }
package/src/verify.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@businessdash/sdk/verify` — the framework-agnostic email / phone
3
+ * verification core.
4
+ *
5
+ * @module
6
+ */
7
+
8
+ export * from "./verify-core/index.js";