@sonordev/site-kit 7.0.1 → 7.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.
Files changed (147) hide show
  1. package/CHANGELOG.md +3606 -0
  2. package/README.md +12 -13
  3. package/agent-manifest.json +11 -5
  4. package/dist/{AnalyticsProvider-EMM2TKRE.js → AnalyticsProvider-ZMQUV33M.js} +4 -4
  5. package/dist/{ArticleViewTracker-RA64BGL6.js → ArticleViewTracker-V7NUXYBY.js} +3 -3
  6. package/dist/{BlocksPopup-D25RFNOV.js → BlocksPopup-52EU7OUY.js} +4 -4
  7. package/dist/{ChatWidget-RYI7BMJJ.js → ChatWidget-5BHMNR57.js} +5 -5
  8. package/dist/{EngageWidget-UKFCN33M.js → EngageWidget-PCGLX7SO.js} +4 -4
  9. package/dist/{FileField-MUHA7LZR.js → FileField-TSFGNAMY.js} +3 -3
  10. package/dist/{FormSpotlight-TCLPWPLL.js → FormSpotlight-XLBWEOTE.js} +1 -1
  11. package/dist/{FormStage-CNYLP6I6.js → FormStage-IJQ5Q2X6.js} +1 -1
  12. package/dist/{ManagedForm-7ZL5SKTO.js → ManagedForm-Z3PKOPIZ.js} +6 -6
  13. package/dist/{ManagedNewsletterForm-33B4JLX7.js → ManagedNewsletterForm-QAO3POLC.js} +4 -4
  14. package/dist/{SignalCore-L5FVDHFE.js → SignalCore-RBA3VDBL.js} +3 -3
  15. package/dist/{SiteDesignReporter-4JOFL4FP.js → SiteDesignReporter-C4LR5X2V.js} +5 -5
  16. package/dist/SitemapSync-XVMGKCF3.js +8 -0
  17. package/dist/_client/booking-widget.js +5 -5
  18. package/dist/affiliates/index.js +3 -3
  19. package/dist/analytics/index.js +4 -4
  20. package/dist/articles/index.js +1 -1
  21. package/dist/articles/server-ui.js +1 -1
  22. package/dist/chat/index.js +5 -5
  23. package/dist/{chunk-FYBZ5SNP.js → chunk-3G2SE2J4.js} +1 -1
  24. package/dist/{chunk-HGCK465A.js → chunk-3J2ERO3I.js} +1 -1
  25. package/dist/{chunk-KXPBMCFL.js → chunk-3QI26673.js} +3 -1
  26. package/dist/{chunk-BMO3VGMR.js → chunk-3XPJKZ6D.js} +30 -7
  27. package/dist/{chunk-QGHSMJKW.js → chunk-4JQQDCMO.js} +1 -1
  28. package/dist/{chunk-6HDT4G4A.js → chunk-4YTYGG2C.js} +2 -2
  29. package/dist/{chunk-N2UVOR3X.js → chunk-662ILEZ6.js} +2 -0
  30. package/dist/chunk-6G43IRWR.js +4 -0
  31. package/dist/{chunk-LVESVYCE.js → chunk-7MHHWZKC.js} +11 -117
  32. package/dist/{chunk-MV2MBTC3.js → chunk-7QTMMHUO.js} +1 -1
  33. package/dist/{chunk-KPAZG65P.js → chunk-CGWUXUYZ.js} +138 -46
  34. package/dist/{chunk-4IQ52CXL.js → chunk-DUAO4Q75.js} +2 -2
  35. package/dist/{chunk-4RMVXRBO.js → chunk-EIULXXUJ.js} +3 -3
  36. package/dist/{chunk-3KUUH2YP.js → chunk-EVFZ7KEW.js} +1 -1
  37. package/dist/{chunk-OFOAHPUV.js → chunk-F42R35NV.js} +1 -1
  38. package/dist/{chunk-QANVUXKH.js → chunk-FLR3EMK6.js} +1 -1
  39. package/dist/{chunk-P4GRY6QP.js → chunk-GIAOPEN6.js} +1 -1
  40. package/dist/{chunk-P5J7VMQ3.js → chunk-GWUKQ26F.js} +1 -1
  41. package/dist/{chunk-TT63HHIT.js → chunk-HAG4YIZY.js} +1 -1
  42. package/dist/{chunk-QZZIKMAT.js → chunk-HVH37YPX.js} +1 -1
  43. package/dist/{chunk-SSUQKA7L.js → chunk-J4D6ZXRW.js} +1 -1
  44. package/dist/{chunk-GYESATRY.js → chunk-L2DJD5Y4.js} +1 -1
  45. package/dist/{chunk-WATH55UY.js → chunk-LFXVE32I.js} +1 -1
  46. package/dist/chunk-LPH5FANE.js +169 -0
  47. package/dist/{chunk-V6LSQRTH.js → chunk-PLUP2KN5.js} +1 -1
  48. package/dist/chunk-RYVDGXC2.js +19 -0
  49. package/dist/{chunk-EGOD74PP.js → chunk-U35H2JIQ.js} +2 -2
  50. package/dist/chunk-VCJYLYJV.js +49 -0
  51. package/dist/{chunk-FL4EPUWA.js → chunk-W2CL2DB3.js} +2 -2
  52. package/dist/{chunk-UZN4ZYR2.js → chunk-XD3ZQET6.js} +1 -1
  53. package/dist/{chunk-CVTVNC2U.js → chunk-XNVSCQ2O.js} +2 -2
  54. package/dist/{chunk-T3MC4HOD.js → chunk-YLSEB32F.js} +1 -1
  55. package/dist/chunk-ZETJTCMV.js +118 -0
  56. package/dist/{chunk-5SEM2V4A.js → chunk-ZIMFQWGJ.js} +3 -3
  57. package/dist/client/index.js +3 -3
  58. package/dist/cms/CmsPage.d.ts +1 -0
  59. package/dist/cms/CmsPreview.d.ts +1 -0
  60. package/dist/cms/CmsSection.d.ts +1 -0
  61. package/dist/cms/index.d.ts +6 -0
  62. package/dist/cms/server-api.d.ts +3 -0
  63. package/dist/commerce/index.js +4 -4
  64. package/dist/config/index.js +1 -1
  65. package/dist/contracts/entries.d.ts +1 -1
  66. package/dist/contracts/site-cache.d.ts +55 -0
  67. package/dist/contracts/site-edit-param.d.ts +7 -0
  68. package/dist/contracts/site-edit.d.ts +77 -0
  69. package/dist/contracts/slot-content.d.ts +111 -0
  70. package/dist/contracts/slots.d.ts +39 -25
  71. package/dist/engage/index.js +6 -6
  72. package/dist/fleet/index.js +4 -4
  73. package/dist/forms/index.js +8 -8
  74. package/dist/forms/server.js +2 -2
  75. package/dist/forms/types.d.ts +3 -1
  76. package/dist/images/index.js +4 -4
  77. package/dist/index.js +1 -1
  78. package/dist/layout/client.js +8 -7
  79. package/dist/layout/index.js +9 -8
  80. package/dist/llms/index.js +4 -2
  81. package/dist/llms/seo-revalidate.d.ts +8 -1
  82. package/dist/maps/index.js +3 -3
  83. package/dist/mcp/sonor.js +6 -6
  84. package/dist/overlay-RXV6U6QC.js +353 -0
  85. package/dist/proxy/index.js +2 -2
  86. package/dist/proxy/securityHeaders.d.ts +4 -0
  87. package/dist/revalidate/index.d.ts +44 -0
  88. package/dist/revalidate/index.js +27 -0
  89. package/dist/seo/ManagedContent.d.ts +2 -0
  90. package/dist/seo/client.js +4 -4
  91. package/dist/seo/index.js +9 -8
  92. package/dist/seo/llms.js +4 -2
  93. package/dist/seo/register-sitemap-cli.js +1 -1
  94. package/dist/seo/server.js +3 -2
  95. package/dist/seo/sitemap.js +2 -2
  96. package/dist/server/index.js +2 -2
  97. package/dist/{server-api-GJJQZVG7.js → server-api-BVCBLJKL.js} +2 -1
  98. package/dist/shared/build-entries.d.ts +1 -0
  99. package/dist/shared/edit-bridge.d.ts +8 -0
  100. package/dist/shared/version.d.ts +1 -1
  101. package/dist/signal/index.js +2 -2
  102. package/dist/sitemap/index.js +2 -2
  103. package/dist/slots/ManagedLink.d.ts +31 -0
  104. package/dist/slots/ManagedList.d.ts +30 -0
  105. package/dist/slots/ManagedRichText.d.ts +31 -0
  106. package/dist/slots/contract.js +2 -1
  107. package/dist/slots/edit/locate.d.ts +30 -0
  108. package/dist/slots/edit/overlay.d.ts +18 -0
  109. package/dist/slots/index.d.ts +12 -4
  110. package/dist/slots/index.js +4 -2
  111. package/dist/slots/revalidate.d.ts +8 -3
  112. package/dist/slots/rich.d.ts +7 -0
  113. package/dist/slots/server-api.d.ts +6 -2
  114. package/dist/sync/index.js +5 -5
  115. package/dist/website/images.js +4 -4
  116. package/dist/website/index.js +5 -5
  117. package/dist/website/popups.js +4 -4
  118. package/dist/website/slots/contract.js +2 -1
  119. package/dist/website/slots.js +4 -2
  120. package/dist/{writeLLMsTxt-UMHKGNRR.js → writeLLMsTxt-QR23OQUE.js} +1 -1
  121. package/docs/MIGRATING-TO-7.md +146 -0
  122. package/docs.json +69 -0
  123. package/package.json +14 -4
  124. package/src/admin-auth/README.md +88 -0
  125. package/src/analytics/README.md +264 -0
  126. package/src/articles/README.md +325 -0
  127. package/src/commerce/README.md +109 -0
  128. package/src/cta-bar/README.md +154 -0
  129. package/src/engage/README.md +241 -0
  130. package/src/forms/README.md +219 -0
  131. package/src/images/README.md +74 -0
  132. package/src/layout/README.md +66 -0
  133. package/src/llms/README.md +723 -0
  134. package/src/mcp/README.md +376 -0
  135. package/src/motion/README.md +372 -0
  136. package/src/og/README.md +304 -0
  137. package/src/proxy/README.md +152 -0
  138. package/src/redirects/README.md +74 -0
  139. package/src/reputation/README.md +64 -0
  140. package/src/revalidate/README.md +82 -0
  141. package/src/seo/README.md +346 -0
  142. package/src/signal/README.md +115 -0
  143. package/src/sitemap/README.md +127 -0
  144. package/src/slots/README.md +168 -0
  145. package/src/sync/README.md +115 -0
  146. package/dist/SitemapSync-7WKY4HXI.js +0 -8
  147. package/dist/chunk-SS636UDN.js +0 -35
@@ -0,0 +1,168 @@
1
+ # Managed copy — `@sonordev/site-kit/website/slots`
2
+
3
+ Let the owner edit a site's words in Sonor without a deploy, while you keep
4
+ the markup. Wrap the copy you'd like managed; the text in your code is the
5
+ fallback, so the page renders exactly as written until someone edits it, and
6
+ whenever Sonor can't be reached.
7
+
8
+ ```tsx
9
+ import { ManagedSlot, ManagedRichText, ManagedLink, ManagedList } from '@sonordev/site-kit/website/slots'
10
+
11
+ <h1 className="hero-title">
12
+ <ManagedSlot id="home-hero-headline" page="/">
13
+ New homes in Cincinnati and Northern Kentucky
14
+ </ManagedSlot>
15
+ </h1>
16
+ ```
17
+
18
+ Every managed region shows up in Sonor under **Website → Content** the first
19
+ time a page renders it. There's no schema to define.
20
+
21
+ ## Four kinds of copy
22
+
23
+ | Component | For | The owner edits |
24
+ |---|---|---|
25
+ | `<ManagedSlot>` | a headline, tagline, label | plain text |
26
+ | `<ManagedRichText>` | an intro, an about section | paragraphs, bold, italic, links, lists |
27
+ | `<ManagedLink>` | a call to action | its label and where it goes |
28
+ | `<ManagedList>` | services, team, FAQs, a gallery | items: title, body, link, image |
29
+
30
+ ### `ManagedRichText`
31
+
32
+ ```tsx
33
+ <div className="prose">
34
+ <ManagedRichText id="about-intro" page="/about">
35
+ {`We've built homes here since **1998**.\n\n- Custom builds\n- Remodels`}
36
+ </ManagedRichText>
37
+ </div>
38
+ ```
39
+
40
+ A string child is read as the same rich-text subset the owner edits, so the
41
+ fallback and the managed copy render alike. It renders `<p>`, `<ul>`, `<ol>`,
42
+ `<strong>`, `<em>`, `<a>` and `<br>`, and nothing else: managed copy is
43
+ parsed into elements, never inserted as HTML.
44
+
45
+ The subset: blank-line paragraphs, single newlines as line breaks,
46
+ `**bold**`, `*italic*`, `[a link](https://…)`, and `-` or `1.` lists.
47
+
48
+ ### `ManagedLink`
49
+
50
+ ```tsx
51
+ <ManagedLink id="home-hero-cta" href="/contact" label="Get a free quote">
52
+ {({ href, label }) => <Link href={href} className="btn">{label}</Link>}
53
+ </ManagedLink>
54
+ ```
55
+
56
+ Without a render function it draws a plain `<a>` with your `className`.
57
+ Managed links may point to `https:`, `http:`, `mailto:`, `tel:`, a `#anchor`
58
+ or a `/path`.
59
+
60
+ ### `ManagedList`
61
+
62
+ ```tsx
63
+ <ManagedList id="home-services" items={SERVICES}>
64
+ {(items) => (
65
+ <ul className="grid">
66
+ {items.map((s) => (
67
+ <li key={s.title}>
68
+ <a href={s.href}>{s.title}</a>
69
+ <p>{s.body}</p>
70
+ </li>
71
+ ))}
72
+ </ul>
73
+ )}
74
+ </ManagedList>
75
+ ```
76
+
77
+ Each item may have a `title`, a plain-text `body`, an `href` and an
78
+ `image: { src, alt, width?, height? }` (https only). Up to 50 items.
79
+
80
+ ## Props every component takes
81
+
82
+ | Prop | |
83
+ |---|---|
84
+ | `id` | stable, lowercase, page-scoped by convention: `home-hero-headline` |
85
+ | `page` | the route it renders on (`"/pricing"`), a literal; Sonor ties the copy to that page's analytics |
86
+ | `site` | the host, on a multi-site project (defaults to `NEXT_PUBLIC_SITE_URL`) |
87
+ | `revalidate` | seconds before a cached value is checked again (default 300); with [Live updates](../revalidate/README.md) installed, edits don't wait for it |
88
+
89
+ ## How it stays fast and safe
90
+
91
+ - **Static.** Copy resolves in the server render with a cached fetch, so a
92
+ page using it stays prerendered. Nothing ships to the browser.
93
+ - **Fallback first.** No key, no network, a bad response, or content of a
94
+ different kind than the code now renders: the fallback shows. It never
95
+ breaks a build or a page.
96
+ - **Signed.** Sonor signs every value with your project key and site-kit
97
+ refuses anything that doesn't verify.
98
+ - **Live in seconds.** Publishing in Sonor calls the site's revalidation
99
+ route, so the change shows without waiting for the next check. It's one
100
+ file, shared with the rest of site-kit (see [Live updates](../revalidate/README.md)):
101
+
102
+ ```ts
103
+ // app/api/seo-revalidate/route.ts
104
+ export { POST } from '@sonordev/site-kit/revalidate'
105
+ ```
106
+
107
+ Without it, a published edit shows once the cached copy ages out (the
108
+ `revalidate` window, five minutes by default).
109
+
110
+ ## Edit on page
111
+
112
+ In Sonor, Website → Content → **Edit on page** opens the live site with its
113
+ managed copy outlined. Click any of it to edit it in a side panel. Text, rich
114
+ text and links show the change on the page as you type, before you publish.
115
+ Links on the page take you to its other pages, still in edit mode.
116
+
117
+ The site needs nothing beyond site-kit 7.1: no login, no secret, no extra
118
+ route. It works like this:
119
+
120
+ - Sonor frames the page with `?sonor_edit=1`. For any other visitor the
121
+ page is unchanged; the overlay is a separate chunk that only loads when
122
+ Sonor frames the page.
123
+ - The overlay finds the copy by its visible text, so your markup and CSS are
124
+ exactly what you wrote (no wrapper elements, no attributes).
125
+ - The page only reports what it found and what was clicked. The edit itself
126
+ is saved by Sonor with the signed-in person's own account.
127
+ - It needs Sonor's dashboard to be allowed to frame the site, which
128
+ `securityHeaders` does by default. A site that sets its own
129
+ `frame-ancestors` should use `DEFAULT_FRAME_ANCESTORS`.
130
+
131
+ ## Lists with photos
132
+
133
+ A list item can carry an image (`{ src, alt, width, height }`). In Sonor,
134
+ **Upload photo** on a list item stores it with the project's files and fills
135
+ in its size, so the page can reserve the space. A gallery is a list:
136
+
137
+ ```tsx
138
+ <ManagedList id="gallery" items={[]}>
139
+ {(items) => (
140
+ <ul className="gallery">
141
+ {items.map((item, i) =>
142
+ item.image ? (
143
+ <li key={item.image.src}>
144
+ <img src={item.image.src} alt={item.image.alt} width={item.image.width} height={item.image.height} loading={i < 3 ? 'eager' : 'lazy'} />
145
+ {item.title && <p>{item.title}</p>}
146
+ </li>
147
+ ) : null,
148
+ )}
149
+ </ul>
150
+ )}
151
+ </ManagedList>
152
+ ```
153
+
154
+ ## Drafts and history
155
+
156
+ Edits in Sonor can be saved as drafts and published when they're ready.
157
+ Every publish is kept, so any earlier version can be restored. Signal can
158
+ propose copy too; proposals wait for approval in Sonor unless the project
159
+ lets it publish on its own.
160
+
161
+ ## Using it without a component
162
+
163
+ ```ts
164
+ import { getSlot } from '@sonordev/site-kit/website/slots'
165
+
166
+ const slot = await getSlot('home-services', { type: 'list' })
167
+ // slot?.value → { items: [...] }, or null (render your fallback)
168
+ ```
@@ -0,0 +1,115 @@
1
+ # Sync (Booking) — `@sonordev/site-kit/sync`
2
+
3
+ Embeddable booking/scheduling widget — like Calendly, built into Sonor. Appointments, consultations, classes.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ 'use client'
9
+ import { BookingWidget } from '@sonordev/site-kit/sync'
10
+
11
+ export default function BookPage() {
12
+ return (
13
+ <BookingWidget
14
+ onBookingComplete={(result) => {
15
+ console.log('Booked:', result.booking.confirmationCode)
16
+ }}
17
+ />
18
+ )
19
+ }
20
+ ```
21
+
22
+ ## Props
23
+
24
+ ```ts
25
+ interface BookingWidgetProps {
26
+ orgSlug?: string // Organization slug (for public mode)
27
+ apiKey?: string // API key (auto-resolved from env)
28
+ apiUrl?: string // Default: https://api.sonor.io
29
+ bookingTypeSlug?: string // Show specific type only
30
+ timezone?: string // Guest timezone (auto-detected)
31
+ className?: string
32
+ daysToShow?: number // Days of availability to display
33
+ onBookingComplete?: (result: BookingResult) => void
34
+ onError?: (error: Error) => void
35
+ hideTypeSelector?: boolean // Hide type selector for single-type embed
36
+
37
+ styles?: { // Custom theme
38
+ primaryColor?: string
39
+ borderRadius?: string
40
+ fontFamily?: string
41
+ backgroundColor?: string
42
+ textPrimary?: string
43
+ borderColor?: string
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## UI Flow
49
+
50
+ 1. **Type selector:** choose a booking type, unless a specific `bookingTypeSlug` is provided.
51
+ 2. **Meeting format:** when the booking type enables meeting options, choose virtual, office, or a visit to the guest's location. Travel meetings validate the address before showing availability.
52
+ 3. **Calendar and times:** choose an available time and reserve it while completing contact details.
53
+ 4. **Guest info:** name, email, phone, and notes. Travel meetings require a phone number.
54
+ 5. **Result:** a confirmed booking shows calendar links when Sonor sends the guest's invitation. When the host's Google Calendar sends it, the widget says who the invite is coming from instead (see [Calendar invitations](#calendar-invitations)). Meetings that require review show a pending request without calendar links.
55
+
56
+ Changing a meeting format restores the selected format, address, and access notes. Guests can return to the service selector when it's available. Contact details survive changes to the format or reserved time.
57
+
58
+ Expired meeting details and time reservations show a recovery action. Guests can recheck their meeting details or choose another time without re-entering their contact information. An active hold remains usable if its prepared meeting token expires, until the hold's own deadline.
59
+
60
+ Navigation is disabled while a reservation or booking request is running. Stale responses cannot reopen a previous step, and holds created after the widget is removed are released.
61
+
62
+ ## API Functions
63
+
64
+ ```ts
65
+ import {
66
+ fetchBookingTypes, fetchBookingTypeDetails,
67
+ fetchAvailability, fetchAvailableDates,
68
+ createSlotHold, releaseSlotHold, createBooking,
69
+ prepareMeeting,
70
+ detectTimezone, formatTime, formatDate, formatDuration,
71
+ } from '@sonordev/site-kit/sync'
72
+ ```
73
+
74
+ ## Types
75
+
76
+ ```ts
77
+ interface BookingType {
78
+ slug: string; name: string; description?: string;
79
+ duration_minutes: number; color?: string;
80
+ location_type: 'virtual' | 'phone' | 'in_person' | 'custom';
81
+ price_cents?: number; currency?: string; is_active: boolean;
82
+ }
83
+
84
+ interface BookingResult {
85
+ success: boolean
86
+ booking: {
87
+ id: string; confirmationCode: string; scheduledAt: string;
88
+ durationMinutes: number; hostName?: string; timezone: string;
89
+ }
90
+ cancelUrl: string; rescheduleUrl: string;
91
+ invitation?: 'google' | 'sonor' // who sends the guest's calendar invite
92
+ calendarLinks: { google: string; outlook: string; ics: string }
93
+ }
94
+ ```
95
+
96
+ ## Calendar invitations
97
+
98
+ Each booking gives the guest one calendar invitation, and `invitation` says who sends it:
99
+
100
+ - **`'google'`**: the host has Google Calendar connected, so Google invites the guest to the host's event. Sonor's emails don't attach a second invite, and the success screen says who the invite is coming from instead of showing calendar links. The Google and Outlook links build a separate event with no tie to that invitation, so a guest who clicked one would have two entries for the same meeting.
101
+ - **`'sonor'`**: there's no Google event for the booking, usually because the host hasn't connected a calendar. Sonor's confirmation email carries the invite, and the success screen shows the calendar links.
102
+
103
+ APIs older than the field don't send it; treat a missing `invitation` like `'sonor'`. `calendarLinks` is still sent for every booking so older widgets keep working. If you build your own success screen with `createBooking`, check `invitation` before you render `calendarLinks`.
104
+
105
+ ## Features
106
+
107
+ - Automatic timezone detection
108
+ - Slot hold with expiry (prevents double-booking)
109
+ - Business hours support
110
+ - Multiple hosts per booking type
111
+ - Custom meeting URL / location
112
+
113
+ ## Booking regression checks
114
+
115
+ Run `pnpm exec vitest run src/sync/booking-flow.test.ts` for expiry and server-error recovery checks. Start `pnpm exec vite --host 127.0.0.1 --port 5189`, then run `node test/meeting-flow/run.mjs` and `node test/meeting-flow/regression.mjs` for the real widget flows. The browser harness intercepts requests to a reserved test domain, so it creates no live bookings.
@@ -1,8 +0,0 @@
1
- export { SitemapSync } from './chunk-WATH55UY.js';
2
- import './chunk-YVKRYQRG.js';
3
- import './chunk-43OCZ3JA.js';
4
- import './chunk-EKBEOXTH.js';
5
- import './chunk-EGOD74PP.js';
6
- import './chunk-3KUUH2YP.js';
7
- import './chunk-QGHSMJKW.js';
8
- import './chunk-PKBMQBKP.js';
@@ -1,35 +0,0 @@
1
- import { createHmac, timingSafeEqual } from 'crypto';
2
-
3
- // src/contracts/slots.ts
4
- var SLOTS_CONTRACT_VERSION = 1;
5
- var SLOT_CONTENT_TYPES = ["text"];
6
- var SLOT_CONTENT_MAX_LENGTH = 1e4;
7
- var SLOT_ID_PATTERN = /^[a-z0-9][a-z0-9-_.:]{0,127}$/;
8
- function isValidSlotId(id) {
9
- return SLOT_ID_PATTERN.test(id);
10
- }
11
- function slotSigningPayload(content) {
12
- return `sonor-slot.v${SLOTS_CONTRACT_VERSION}
13
- ${content.slot_id}
14
- ${content.type}
15
- ${content.content}`;
16
- }
17
- function signSlotContent(content, apiKey) {
18
- return createHmac("sha256", apiKey).update(slotSigningPayload(content), "utf8").digest("hex");
19
- }
20
- function verifySlotContent(content, apiKey) {
21
- if (!content || typeof content !== "object") return false;
22
- const c = content;
23
- if (typeof c.slot_id !== "string" || !isValidSlotId(c.slot_id) || typeof c.content !== "string" || c.content.length === 0 || c.content.length > SLOT_CONTENT_MAX_LENGTH || typeof c.signature !== "string" || !SLOT_CONTENT_TYPES.includes(c.type)) {
24
- return false;
25
- }
26
- const expected = signSlotContent(
27
- { slot_id: c.slot_id, type: c.type, content: c.content },
28
- apiKey
29
- );
30
- const a = Buffer.from(expected, "utf8");
31
- const b = Buffer.from(c.signature, "utf8");
32
- return a.length === b.length && timingSafeEqual(a, b);
33
- }
34
-
35
- export { SLOTS_CONTRACT_VERSION, SLOT_CONTENT_MAX_LENGTH, SLOT_CONTENT_TYPES, SLOT_ID_PATTERN, isValidSlotId, signSlotContent, slotSigningPayload, verifySlotContent };