@proveanything/smartlinks 2.1.6 → 2.1.8
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/dist/docs/API_SUMMARY.md +30 -1
- package/dist/docs/bookings.md +16 -0
- package/dist/docs/comms.md +28 -2
- package/dist/docs/iframe-responder.md +23 -7
- package/dist/docs/server-functions.md +29 -0
- package/dist/docs/websites.md +7 -1
- package/dist/iframe.js +74 -28
- package/dist/iframeResponder.d.ts +6 -0
- package/dist/iframeResponder.js +35 -4
- package/dist/openapi.yaml +24 -0
- package/dist/types/bookings.d.ts +2 -0
- package/dist/types/broadcasts.d.ts +4 -0
- package/dist/types/comms.d.ts +28 -0
- package/dist/types/iframeResponder.d.ts +15 -0
- package/docs/API_SUMMARY.md +30 -1
- package/docs/bookings.md +16 -0
- package/docs/comms.md +28 -2
- package/docs/iframe-responder.md +23 -7
- package/docs/server-functions.md +29 -0
- package/docs/websites.md +7 -1
- package/openapi.yaml +24 -0
- package/package.json +87 -87
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8 | Generated: 2026-11-02T13:05:01.425Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -5402,6 +5402,7 @@ interface MessageBookersInput {
|
|
|
5402
5402
|
resourceId?: string
|
|
5403
5403
|
statuses?: BookingStatus[]
|
|
5404
5404
|
ref?: string
|
|
5405
|
+
attach?: import('./comms').EmailAttachmentSpec[]
|
|
5405
5406
|
}
|
|
5406
5407
|
```
|
|
5407
5408
|
|
|
@@ -5512,6 +5513,7 @@ interface BroadcastRecord {
|
|
|
5512
5513
|
priority?: number
|
|
5513
5514
|
}>
|
|
5514
5515
|
}
|
|
5516
|
+
attach?: Array<{ asset: string }>
|
|
5515
5517
|
[key: string]: unknown
|
|
5516
5518
|
}
|
|
5517
5519
|
createdAt: string
|
|
@@ -6242,6 +6244,7 @@ interface TransactionalSendRequest {
|
|
|
6242
6244
|
}
|
|
6243
6245
|
ref?: string
|
|
6244
6246
|
appId?: string
|
|
6247
|
+
attach?: EmailAttachmentSpec[]
|
|
6245
6248
|
}
|
|
6246
6249
|
```
|
|
6247
6250
|
|
|
@@ -6251,6 +6254,7 @@ interface TransactionalSendResponse {
|
|
|
6251
6254
|
ok: true
|
|
6252
6255
|
channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
|
|
6253
6256
|
messageId?: string
|
|
6257
|
+
attachmentsLeftOut?: string[]
|
|
6254
6258
|
}
|
|
6255
6259
|
```
|
|
6256
6260
|
|
|
@@ -6270,6 +6274,8 @@ interface TransactionalSendError {
|
|
|
6270
6274
|
|
|
6271
6275
|
**ConsentChannels** = `Partial<Record<BroadcastChannel, boolean>>`
|
|
6272
6276
|
|
|
6277
|
+
**EmailAttachmentSpec** = ``
|
|
6278
|
+
|
|
6273
6279
|
**TransactionalSendResult** = `// src/types/comms.ts
|
|
6274
6280
|
// Communication and notification types for the Smartlinks API
|
|
6275
6281
|
import type { IdField } from './common'
|
|
@@ -6595,14 +6601,36 @@ export interface TransactionalSendRequest {
|
|
|
6595
6601
|
ref?: string
|
|
6596
6602
|
/** App context stored on the comms-events row */
|
|
6597
6603
|
appId?: string
|
|
6604
|
+
/**
|
|
6605
|
+
* Files to attach (email only; at most 5, 10 MB in all). What can't be made is left out and listed in
|
|
6606
|
+
* `attachmentsLeftOut` — the email still goes.
|
|
6607
|
+
*/
|
|
6608
|
+
attach?: EmailAttachmentSpec[]
|
|
6598
6609
|
}
|
|
6599
6610
|
|
|
6611
|
+
/**
|
|
6612
|
+
* One file for an email:
|
|
6613
|
+
* - `'calendar'` — the booking's `.ics` calendar invite (needs `include.booking` and `appId`)
|
|
6614
|
+
* - `{ asset }` — a file uploaded to this collection's assets
|
|
6615
|
+
* - `{ function }` — the app's (`appId`) own server function makes it (see server-functions.md, "Making an email attachment")
|
|
6616
|
+
* - `{ filename, contentType, content }` — a file you made, base64 (PDF, Office documents, images, text, CSV, calendar)
|
|
6617
|
+
* - `{ filename, file }` — a private file you made (e.g. `pdf.create` with `return: 'signedUrl'`)
|
|
6618
|
+
*/
|
|
6619
|
+
export type EmailAttachmentSpec =
|
|
6620
|
+
| 'calendar'
|
|
6621
|
+
| { asset: string }
|
|
6622
|
+
| { function: string }
|
|
6623
|
+
| { filename: string; contentType: string; content: string }
|
|
6624
|
+
| { filename: string; contentType?: string; file: string }
|
|
6625
|
+
|
|
6600
6626
|
export interface TransactionalSendResponse {
|
|
6601
6627
|
ok: true
|
|
6602
6628
|
/** The channel the message was actually sent on */
|
|
6603
6629
|
channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
|
|
6604
6630
|
/** Provider message ID (email/SMS/WhatsApp); absent for push/wallet */
|
|
6605
6631
|
messageId?: string
|
|
6632
|
+
/** Attachments that couldn't be made, and why (the email went without them) */
|
|
6633
|
+
attachmentsLeftOut?: string[]
|
|
6606
6634
|
}
|
|
6607
6635
|
|
|
6608
6636
|
export interface TransactionalSendError {
|
|
@@ -7511,6 +7539,7 @@ interface IframeResponderOptions {
|
|
|
7511
7539
|
) => Promise<void>;
|
|
7512
7540
|
onAuthLogout?: () => Promise<void>;
|
|
7513
7541
|
onRouteChange?: (path: string, state: Record<string, string>) => void;
|
|
7542
|
+
autoHeight?: boolean | { minHeight?: number };
|
|
7514
7543
|
onResize?: (height: number) => void;
|
|
7515
7544
|
onError?: (error: Error) => void;
|
|
7516
7545
|
onReady?: () => void;
|
package/dist/docs/bookings.md
CHANGED
|
@@ -368,6 +368,17 @@ wording.
|
|
|
368
368
|
A **public** resource record (a staff profile the booking page lists) keeps its address on a
|
|
369
369
|
**private child record** instead (`visibility: 'admin'`, `parentType: 'record'`, `parentId` = the
|
|
370
370
|
resource) — the platform looks there too, and the address never ships with the public listing.
|
|
371
|
+
- Any step can also take `replyTo` — an address, or `"contact"` (replies go to the person the event is
|
|
372
|
+
about: a team notification answered straight to the booker) — and `cc` / `bcc` (written-out addresses,
|
|
373
|
+
same rules as `to`, counted in the same daily cap; over it the person still gets their email, without the
|
|
374
|
+
copies). A site's `cloud/email.json` (`{ "fromName", "replyTo" }`) sets the name the business's emails come
|
|
375
|
+
from and their default reply-to; the From address is the business's verified domain.
|
|
376
|
+
- `attach` on a step adds files (at most 5, 10 MB in all): `"calendar"` — the booking as an `.ics` invite
|
|
377
|
+
that drops straight into the person's calendar (a cancelled booking's invite cancels it there);
|
|
378
|
+
`{ "asset": "<assetId>" }` — a file in the collection's assets (terms, a price list);
|
|
379
|
+
`{ "function": "<name>" }` — your app's own function makes it (a receipt PDF: server-functions.md,
|
|
380
|
+
"Making an email attachment"). E.g. `{ "send": "booking-confirmed", "to": "contact", "attach": ["calendar"] }`.
|
|
381
|
+
One that can't be made is left out; the email still goes.
|
|
371
382
|
- Who gets what, in templates: `{{ booking }}` is the booking **ready-formatted**: its fields and
|
|
372
383
|
`data`, plus `serviceName`, `staffName`, `customer`, `durationMinutes`, `when` (`date`,
|
|
373
384
|
`shortDate`, `time`, `endTime`, `dateTime`, `timezone` — in the business's timezone) and, when the
|
|
@@ -393,9 +404,14 @@ window (optionally with one resource), each hydrated with **their own** booking.
|
|
|
393
404
|
await SL.bookings.message(collectionId, appId, { templateId, slotId })
|
|
394
405
|
// Everyone booked with Sam next week, by email even if they prefer texts
|
|
395
406
|
await SL.bookings.message(collectionId, appId, { templateId, channel: 'email', from, to, resourceType: 'record', resourceId: samId })
|
|
407
|
+
// Everyone on the class, each with their own calendar invite
|
|
408
|
+
await SL.bookings.message(collectionId, appId, { templateId, slotId, attach: ['calendar'] })
|
|
396
409
|
// → { matched, sent, skipped: { noContact, duplicate, failed }, failures: [{ bookingId, reason }] }
|
|
397
410
|
```
|
|
398
411
|
|
|
412
|
+
`attach` takes the same files as `comms.sendTransactional` (comms.md, "Attachments"); `'calendar'` is
|
|
413
|
+
each person's own booking.
|
|
414
|
+
|
|
399
415
|
Statuses default to held, pending and confirmed; at most 500 recipients per call (narrow the window
|
|
400
416
|
for more). One message per contact on a slot, one per booking in a window. Also
|
|
401
417
|
`ctx.sl.bookings.message(input)` in a server function (`sl:bookings:write`) — a cron function that
|
package/dist/docs/comms.md
CHANGED
|
@@ -56,6 +56,7 @@ if (result.ok) {
|
|
|
56
56
|
| `include` | object | Hydration flags — see table below. |
|
|
57
57
|
| `ref` | `string` | Arbitrary reference string stored with the event. |
|
|
58
58
|
| `appId` | `string` | App identifier, used for context/logging. |
|
|
59
|
+
| `attach` | `EmailAttachmentSpec[]` | Files on the email — see [Attachments](#attachments). |
|
|
59
60
|
|
|
60
61
|
**`include` fields:**
|
|
61
62
|
|
|
@@ -73,12 +74,36 @@ if (result.ok) {
|
|
|
73
74
|
|
|
74
75
|
```typescript
|
|
75
76
|
// Success
|
|
76
|
-
{ ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string }
|
|
77
|
+
{ ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string; attachmentsLeftOut?: string[] }
|
|
77
78
|
|
|
78
79
|
// Failure
|
|
79
80
|
{ ok: false; error: string }
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
### Attachments
|
|
84
|
+
|
|
85
|
+
`attach` puts files on the email (email only — ignored when the send goes by text or push). At most 5
|
|
86
|
+
files and 10 MB in all; PDF, Word / Excel / PowerPoint, images, text, CSV and calendar files.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
await comms.sendTransactional(collectionId, {
|
|
90
|
+
contactId, templateId: 'booking-confirmed', appId: 'appointment-booking',
|
|
91
|
+
include: { booking: bookingId },
|
|
92
|
+
attach: [
|
|
93
|
+
'calendar', // the booking's .ics invite (needs include.booking + appId)
|
|
94
|
+
{ asset: 'a1b2c3' }, // a file uploaded to the collection's assets
|
|
95
|
+
{ function: 'receiptPdf' }, // the app's own server function makes it
|
|
96
|
+
{ filename: 'Quote.pdf', contentType: 'application/pdf', content: base64 }, // a file you made
|
|
97
|
+
{ filename: 'Report.pdf', file: signedUrl }, // a private file you made (pdf.create return: 'signedUrl')
|
|
98
|
+
],
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A malformed list is refused (`transactional.attach_invalid`). A file that can't be made at send time
|
|
103
|
+
(a missing asset, a function that fails) is left out and listed in `attachmentsLeftOut` — the email
|
|
104
|
+
still goes. The request body is capped at 10 MB, so for big files make a private file and pass `file`.
|
|
105
|
+
`bookings.message` takes the same `attach` (`'calendar'` is each person's own booking).
|
|
106
|
+
|
|
82
107
|
---
|
|
83
108
|
|
|
84
109
|
## Broadcasts
|
|
@@ -149,11 +174,12 @@ Consent resolution order (per recipient, per channel):
|
|
|
149
174
|
3. `preferences._default.topics[topic]`
|
|
150
175
|
4. Subject-specific preferences (when hydrating a subject context)
|
|
151
176
|
|
|
152
|
-
Minimal broadcast data shape:
|
|
177
|
+
Minimal broadcast data shape (`attach` is optional — uploaded assets only, the same files on every email):
|
|
153
178
|
```json
|
|
154
179
|
{
|
|
155
180
|
"data": {
|
|
156
181
|
"topic": "newsletter",
|
|
182
|
+
"attach": [{ "asset": "a1b2c3" }],
|
|
157
183
|
"channelSettings": {
|
|
158
184
|
"mode": "preferred",
|
|
159
185
|
"channels": [
|
|
@@ -35,10 +35,8 @@ const responder = new smartlinks.IframeResponder({
|
|
|
35
35
|
smartlinks.setBearerToken(token);
|
|
36
36
|
console.log('User logged in:', user);
|
|
37
37
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
iframeElement.style.height = `${height}px`;
|
|
41
|
-
},
|
|
38
|
+
// An app in a page: the iframe is as tall as the app's content — the page scrolls, never the iframe.
|
|
39
|
+
autoHeight: { minHeight: 480 },
|
|
42
40
|
});
|
|
43
41
|
|
|
44
42
|
// Attach to iframe element
|
|
@@ -115,9 +113,24 @@ interface IframeResponderOptions {
|
|
|
115
113
|
onResize?: (height: number) => void;
|
|
116
114
|
onError?: (error: Error) => void;
|
|
117
115
|
onReady?: () => void;
|
|
116
|
+
|
|
117
|
+
// Height
|
|
118
|
+
autoHeight?: boolean | { minHeight?: number }; // size the iframe to the app's content (an app in a page)
|
|
118
119
|
}
|
|
119
120
|
```
|
|
120
121
|
|
|
122
|
+
**Height: `autoHeight` or `onResize`.**
|
|
123
|
+
|
|
124
|
+
- **`autoHeight`** — for an app placed in a page (a website, a section of a dashboard). The responder sets
|
|
125
|
+
the iframe's height from the height the app reports, adds the iframe's own border and padding (an
|
|
126
|
+
element with `box-sizing: border-box`, Tailwind's default, otherwise ends up short by its border — a
|
|
127
|
+
pointless scrollbar), and never uses the window-filling height. The iframe's own scrolling stays on as a
|
|
128
|
+
fallback for an app on an older SDK that under-reports its height. `minHeight`
|
|
129
|
+
is the least height it gives. `onResize` still hears each content height.
|
|
130
|
+
- **`onResize`** without `autoHeight` — for a full-screen host that sizes the iframe itself. It receives
|
|
131
|
+
the app's content height whenever the app reports one, **and** on every window resize the window's
|
|
132
|
+
height below the iframe's container.
|
|
133
|
+
|
|
121
134
|
## Advanced Examples
|
|
122
135
|
|
|
123
136
|
### Deep Linking / Route Synchronization
|
|
@@ -175,6 +188,8 @@ const responder = new smartlinks.IframeResponder({
|
|
|
175
188
|
|
|
176
189
|
### Responsive Height Management
|
|
177
190
|
|
|
191
|
+
An app in a page: `autoHeight` (above) does it all. A full-screen host that manages the height itself:
|
|
192
|
+
|
|
178
193
|
```typescript
|
|
179
194
|
const responder = new smartlinks.IframeResponder({
|
|
180
195
|
collectionId: 'acme-wines',
|
|
@@ -442,7 +457,7 @@ import type {
|
|
|
442
457
|
3. **Clean up** responder with `destroy()` when unmounting
|
|
443
458
|
4. **Use version control** to test against development versions
|
|
444
459
|
5. **Validate tokens** server-side for security
|
|
445
|
-
6. **
|
|
460
|
+
6. **Size the iframe to its content** with `autoHeight` (an app in a page) or `onResize` (a full-screen host)
|
|
446
461
|
7. **Sync routes** with parent navigation for better UX
|
|
447
462
|
8. **Set `parentPath`** for routes that have a meaningful parent screen in the app hierarchy
|
|
448
463
|
|
|
@@ -458,8 +473,9 @@ import type {
|
|
|
458
473
|
- Verify token is being set with `setBearerToken()`
|
|
459
474
|
- Check that auth messages are being received
|
|
460
475
|
|
|
461
|
-
### Height not updating
|
|
462
|
-
-
|
|
476
|
+
### Height not updating, or a scrollbar inside the iframe
|
|
477
|
+
- An app in a page: use `autoHeight` instead of setting the height yourself
|
|
478
|
+
- A full-screen host: implement the `onResize` callback
|
|
463
479
|
- Ensure iframe has CSS styling for height changes
|
|
464
480
|
- Check that iframe is sending resize messages
|
|
465
481
|
|
|
@@ -703,6 +703,35 @@ export async function printLabel(ctx, event) {
|
|
|
703
703
|
- **Verify:** `pdf.decodeBarcodes` on a `signedUrl` reads the codes back deterministically, so you can
|
|
704
704
|
confirm the digits before answering.
|
|
705
705
|
|
|
706
|
+
### Making an email attachment
|
|
707
|
+
|
|
708
|
+
A flow step can attach a file your function makes: `"attach": [{ "function": "receiptPdf" }]` in a
|
|
709
|
+
site's `cloud/flows.json` (or an app's blueprint). When the email goes, the platform calls your function
|
|
710
|
+
(your app's, in this collection, as the collection, like a connector step) with:
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
event.body = { purpose: 'attachment', eventType, interactionId, outcome, contactId, metadata, booking? } // booking: ready-formatted
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
and attaches what it returns:
|
|
717
|
+
|
|
718
|
+
```ts
|
|
719
|
+
// capabilities: ["tools:pdf.create"]
|
|
720
|
+
export async function receiptPdf(ctx, event) {
|
|
721
|
+
const b = event.body.booking
|
|
722
|
+
const pdf = await ctx.sl.tools.run('pdf.create', { format: 'A4', html: `<h1>Receipt</h1><p>${b.serviceName}, ${b.when.dateTime}</p>`, return: 'signedUrl' })
|
|
723
|
+
return { filename: `receipt-${b.id}.pdf`, file: pdf.url } // a private file you made
|
|
724
|
+
// or { filename, contentType: 'application/pdf', content: '<base64>' }, a list of those, or null for none
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
- Files: PDF, calendar (`.ics`), images, text / CSV and Office documents; at most 5 per email and 10 MB
|
|
729
|
+
in all. A private file must be one this platform made (`return: 'signedUrl'`), never an outside URL.
|
|
730
|
+
- If your function fails or returns something else, the email still goes without it (the reason is in
|
|
731
|
+
your function's runs).
|
|
732
|
+
- Other sources need no code: `"calendar"` (a booking event's calendar invite) and
|
|
733
|
+
`{ "asset": "<assetId>" }` (a file in the collection's assets) — see bookings.md, "Flows on booking events".
|
|
734
|
+
|
|
706
735
|
---
|
|
707
736
|
|
|
708
737
|
## Worked example — a public competition entry
|
package/dist/docs/websites.md
CHANGED
|
@@ -80,11 +80,17 @@ const responder = new SL.IframeResponder({
|
|
|
80
80
|
appId: 'appointments', // the app's id on this collection
|
|
81
81
|
initialPath: '/my-bookings', // the page's `path`
|
|
82
82
|
params: { service: 'svc_123' }, // what the page `accepts`
|
|
83
|
-
|
|
83
|
+
autoHeight: { minHeight: 480 }, // the iframe is as tall as the page: your page scrolls, never the iframe
|
|
84
84
|
})
|
|
85
85
|
iframe.src = await responder.attach(iframe)
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
- **Size it with `autoHeight`, not by hand.** The responder sets the iframe's height from the page's own
|
|
89
|
+
height and counts the iframe's border and padding, so there's nothing left to scroll. Setting
|
|
90
|
+
`iframe.style.height` from `onResize` yourself goes wrong in a site: `onResize` also fires on every
|
|
91
|
+
window resize with "the window below the iframe" (for a full-screen host), and a bordered iframe
|
|
92
|
+
(Tailwind's border-box) ends up a few pixels short — a scrollbar inside the frame.
|
|
93
|
+
|
|
88
94
|
- **The visitor's session goes with it.** The page's API calls are proxied through the site, so it
|
|
89
95
|
acts as whoever is signed in to the site (or as a guest). Never pass a token in the URL.
|
|
90
96
|
- **The app must be installed** on the site's collection; its address comes from there.
|
package/dist/iframe.js
CHANGED
|
@@ -36,16 +36,29 @@ export var iframe;
|
|
|
36
36
|
function sendHeight(height, extra) {
|
|
37
37
|
if (!inIframe())
|
|
38
38
|
return;
|
|
39
|
-
const h = height !== null && height !== void 0 ? height :
|
|
39
|
+
const h = height !== null && height !== void 0 ? height : measureHeight();
|
|
40
40
|
postParentMessage((resizeOptions === null || resizeOptions === void 0 ? void 0 : resizeOptions.messageType) || 'smartlinks:resize', Object.assign(Object.assign({ height: h }, resizeOptions === null || resizeOptions === void 0 ? void 0 : resizeOptions.extra), extra));
|
|
41
41
|
}
|
|
42
42
|
iframe.sendHeight = sendHeight;
|
|
43
|
+
/**
|
|
44
|
+
* The page's own height — not the frame's. scrollHeight and clientHeight are never less than the iframe's
|
|
45
|
+
* viewport, so a page that got shorter (a step done, a list emptied) was still reported at the frame's old
|
|
46
|
+
* height and the frame never shrank. When the page overflows the frame, its scrollHeight is the real
|
|
47
|
+
* height (absolutely positioned content included); when it fits, the document's own box is.
|
|
48
|
+
*/
|
|
43
49
|
function measureHeight() {
|
|
44
50
|
if (!isBrowser())
|
|
45
51
|
return 0;
|
|
46
52
|
const doc = document.documentElement;
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
if (doc.scrollHeight > doc.clientHeight + 1)
|
|
54
|
+
return doc.scrollHeight;
|
|
55
|
+
const body = document.body;
|
|
56
|
+
let bodyHeight = 0;
|
|
57
|
+
if (body) {
|
|
58
|
+
const cs = window.getComputedStyle(body);
|
|
59
|
+
bodyHeight = body.offsetHeight + (parseFloat(cs.marginTop) || 0) + (parseFloat(cs.marginBottom) || 0);
|
|
60
|
+
}
|
|
61
|
+
return Math.ceil(Math.max(doc.offsetHeight, bodyHeight));
|
|
49
62
|
}
|
|
50
63
|
function scheduleManualPolling() {
|
|
51
64
|
var _a;
|
|
@@ -61,33 +74,63 @@ export var iframe;
|
|
|
61
74
|
}
|
|
62
75
|
}, interval);
|
|
63
76
|
}
|
|
77
|
+
let measureQueued = false;
|
|
78
|
+
/**
|
|
79
|
+
* Measure once, a moment after a burst of changes, however many asked for it. A timer, not
|
|
80
|
+
* requestAnimationFrame: frames are paused in a hidden page and in an off-screen cross-origin iframe (an
|
|
81
|
+
* app below the fold), so the height wasn't reported until it was scrolled into view; timers only slow down.
|
|
82
|
+
*/
|
|
83
|
+
function queueMeasure() {
|
|
84
|
+
if (measureQueued || !isBrowser())
|
|
85
|
+
return;
|
|
86
|
+
measureQueued = true;
|
|
87
|
+
const run = () => {
|
|
88
|
+
measureQueued = false;
|
|
89
|
+
if (!resizeOptions)
|
|
90
|
+
return;
|
|
91
|
+
const h = measureHeight();
|
|
92
|
+
if (resizeOptions.alwaysSend || h !== lastHeight) {
|
|
93
|
+
lastHeight = h;
|
|
94
|
+
sendHeight(h);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
setTimeout(run, 30);
|
|
98
|
+
}
|
|
99
|
+
let lastWidth = 0;
|
|
100
|
+
/** The frame got wider or narrower: the page reflows. (Its height is ours to set — not a reason.) */
|
|
101
|
+
function onWindowResize() {
|
|
102
|
+
if (window.innerWidth === lastWidth)
|
|
103
|
+
return;
|
|
104
|
+
lastWidth = window.innerWidth;
|
|
105
|
+
queueMeasure();
|
|
106
|
+
}
|
|
107
|
+
const CONTENT_EVENTS = ['load', 'transitionend', 'animationend'];
|
|
108
|
+
/**
|
|
109
|
+
* Re-measure when something that changes the page's height happens: its content (any DOM change — a step
|
|
110
|
+
* rendered, times loaded), an image or font arriving, a transition or animation ending, the frame's width.
|
|
111
|
+
* Never the frame's own height: an app held at the frame's height (html/body/root at 100%, a 100vh
|
|
112
|
+
* wrapper — common in app templates) changes size whenever the host resizes the frame, so watching it
|
|
113
|
+
* re-measured after every resize and, with a few px beyond 100vh, grew the frame step by step. Content
|
|
114
|
+
* growing INSIDE such a pinned body never resized it either, so it was never reported (the booking app
|
|
115
|
+
* said "700" once while loading and nothing after).
|
|
116
|
+
*/
|
|
64
117
|
function setupObservers() {
|
|
65
118
|
if (!isBrowser())
|
|
66
119
|
return;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const h = measureHeight();
|
|
71
|
-
if ((resizeOptions === null || resizeOptions === void 0 ? void 0 : resizeOptions.alwaysSend) || h !== lastHeight) {
|
|
72
|
-
lastHeight = h;
|
|
73
|
-
sendHeight(h);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
resizeObserver.observe(document.body);
|
|
120
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
121
|
+
mutationObserver = new MutationObserver(queueMeasure);
|
|
122
|
+
mutationObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });
|
|
77
123
|
}
|
|
78
124
|
else {
|
|
79
|
-
// Fallback: MutationObserver for DOM changes
|
|
80
|
-
mutationObserver = new MutationObserver(() => {
|
|
81
|
-
const h = measureHeight();
|
|
82
|
-
if ((resizeOptions === null || resizeOptions === void 0 ? void 0 : resizeOptions.alwaysSend) || h !== lastHeight) {
|
|
83
|
-
lastHeight = h;
|
|
84
|
-
sendHeight(h);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
88
|
-
// Manual polling as additional safeguard
|
|
89
125
|
scheduleManualPolling();
|
|
90
126
|
}
|
|
127
|
+
for (const e of CONTENT_EVENTS)
|
|
128
|
+
document.addEventListener(e, queueMeasure, true);
|
|
129
|
+
const fonts = document.fonts;
|
|
130
|
+
if (fonts && typeof fonts.addEventListener === 'function')
|
|
131
|
+
fonts.addEventListener('loadingdone', queueMeasure);
|
|
132
|
+
lastWidth = window.innerWidth;
|
|
133
|
+
window.addEventListener('resize', onWindowResize);
|
|
91
134
|
}
|
|
92
135
|
/** Enable automatic height reporting to parent iframe. */
|
|
93
136
|
function enableAutoIframeResize(options) {
|
|
@@ -97,10 +140,6 @@ export var iframe;
|
|
|
97
140
|
lastHeight = measureHeight();
|
|
98
141
|
sendHeight(lastHeight);
|
|
99
142
|
setupObservers();
|
|
100
|
-
if (!resizeObserver) {
|
|
101
|
-
// If no ResizeObserver, MutationObserver is active and we also poll
|
|
102
|
-
scheduleManualPolling();
|
|
103
|
-
}
|
|
104
143
|
}
|
|
105
144
|
iframe.enableAutoIframeResize = enableAutoIframeResize;
|
|
106
145
|
/** Disable automatic height reporting. */
|
|
@@ -109,8 +148,15 @@ export var iframe;
|
|
|
109
148
|
resizeObserver.disconnect();
|
|
110
149
|
if (mutationObserver)
|
|
111
150
|
mutationObserver.disconnect();
|
|
112
|
-
if (isBrowser())
|
|
151
|
+
if (isBrowser()) {
|
|
152
|
+
for (const e of CONTENT_EVENTS)
|
|
153
|
+
document.removeEventListener(e, queueMeasure, true);
|
|
154
|
+
const fonts = document.fonts;
|
|
155
|
+
if (fonts && typeof fonts.removeEventListener === 'function')
|
|
156
|
+
fonts.removeEventListener('loadingdone', queueMeasure);
|
|
157
|
+
window.removeEventListener('resize', onWindowResize);
|
|
113
158
|
clearInterval(autoResizeTimer);
|
|
159
|
+
}
|
|
114
160
|
resizeObserver = undefined;
|
|
115
161
|
mutationObserver = undefined;
|
|
116
162
|
autoResizeTimer = undefined;
|
|
@@ -61,6 +61,12 @@ export declare class IframeResponder {
|
|
|
61
61
|
private resolveAppUrl;
|
|
62
62
|
private getVersionUrl;
|
|
63
63
|
private buildIframeSrc;
|
|
64
|
+
/**
|
|
65
|
+
* autoHeight: the iframe exactly as tall as the app's content. The height set on an element with
|
|
66
|
+
* box-sizing: border-box (Tailwind's default) includes its border and padding, so those are added — a
|
|
67
|
+
* 1px border otherwise left the page 2px short and the iframe showed a pointless scrollbar.
|
|
68
|
+
*/
|
|
69
|
+
private applyContentHeight;
|
|
64
70
|
private calculateViewportHeight;
|
|
65
71
|
private handleMessage;
|
|
66
72
|
private handleRouteChange;
|
package/dist/iframeResponder.js
CHANGED
|
@@ -77,10 +77,15 @@ export class IframeResponder {
|
|
|
77
77
|
// Set up message listener
|
|
78
78
|
this.messageHandler = this.handleMessage.bind(this);
|
|
79
79
|
window.addEventListener('message', this.messageHandler);
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
// autoHeight: sized to the app's content, so the iframe has nothing to scroll. Its own scrolling is
|
|
81
|
+
// left on: an app on an older SDK may still under-report its height, and a scrollbar beats content
|
|
82
|
+
// cut off.
|
|
83
|
+
if (!this.options.autoHeight) {
|
|
84
|
+
// Set up resize listener for viewport-based calculations (a full-screen host)
|
|
85
|
+
this.resizeHandler = this.calculateViewportHeight.bind(this);
|
|
86
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
87
|
+
window.addEventListener('orientationchange', this.resizeHandler);
|
|
88
|
+
}
|
|
84
89
|
const src = this.buildIframeSrc();
|
|
85
90
|
return src;
|
|
86
91
|
}
|
|
@@ -239,6 +244,30 @@ export class IframeResponder {
|
|
|
239
244
|
// ===========================================================================
|
|
240
245
|
// Viewport Resize Calculation
|
|
241
246
|
// ===========================================================================
|
|
247
|
+
/**
|
|
248
|
+
* autoHeight: the iframe exactly as tall as the app's content. The height set on an element with
|
|
249
|
+
* box-sizing: border-box (Tailwind's default) includes its border and padding, so those are added — a
|
|
250
|
+
* 1px border otherwise left the page 2px short and the iframe showed a pointless scrollbar.
|
|
251
|
+
*/
|
|
252
|
+
applyContentHeight(contentHeight) {
|
|
253
|
+
var _a;
|
|
254
|
+
const iframe = this.iframe;
|
|
255
|
+
if (!iframe)
|
|
256
|
+
return;
|
|
257
|
+
const min = typeof this.options.autoHeight === 'object' ? (_a = this.options.autoHeight.minHeight) !== null && _a !== void 0 ? _a : 0 : 0;
|
|
258
|
+
let extra = 0;
|
|
259
|
+
try {
|
|
260
|
+
const cs = window.getComputedStyle(iframe);
|
|
261
|
+
if (cs.boxSizing === 'border-box') {
|
|
262
|
+
extra = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom']
|
|
263
|
+
.reduce((n, k) => n + (parseFloat(cs[k]) || 0), 0);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (_b) {
|
|
267
|
+
/* no computed style (not in a document): the content height as it is */
|
|
268
|
+
}
|
|
269
|
+
iframe.style.height = `${Math.ceil(Math.max(min, contentHeight + extra))}px`;
|
|
270
|
+
}
|
|
242
271
|
calculateViewportHeight() {
|
|
243
272
|
var _a, _b;
|
|
244
273
|
if (!this.iframe)
|
|
@@ -332,6 +361,8 @@ export class IframeResponder {
|
|
|
332
361
|
case 'smartlinks:resize': {
|
|
333
362
|
const contentHeight = (_a = data.payload) === null || _a === void 0 ? void 0 : _a.height;
|
|
334
363
|
if (typeof contentHeight === 'number' && contentHeight > 0) {
|
|
364
|
+
if (this.options.autoHeight)
|
|
365
|
+
this.applyContentHeight(contentHeight);
|
|
335
366
|
(_c = (_b = this.options).onResize) === null || _c === void 0 ? void 0 : _c.call(_b, contentHeight);
|
|
336
367
|
}
|
|
337
368
|
break;
|
package/dist/openapi.yaml
CHANGED
|
@@ -24805,6 +24805,11 @@ components:
|
|
|
24805
24805
|
$ref: "#/components/schemas/BookingStatus"
|
|
24806
24806
|
ref:
|
|
24807
24807
|
type: string
|
|
24808
|
+
attach:
|
|
24809
|
+
type: array
|
|
24810
|
+
items:
|
|
24811
|
+
type: object
|
|
24812
|
+
additionalProperties: true
|
|
24808
24813
|
required:
|
|
24809
24814
|
- templateId
|
|
24810
24815
|
MessageBookersResult:
|
|
@@ -24999,6 +25004,11 @@ components:
|
|
|
24999
25004
|
type: boolean
|
|
25000
25005
|
priority:
|
|
25001
25006
|
type: number
|
|
25007
|
+
attach:
|
|
25008
|
+
type: array
|
|
25009
|
+
items:
|
|
25010
|
+
type: object
|
|
25011
|
+
additionalProperties: true
|
|
25002
25012
|
createdAt:
|
|
25003
25013
|
type: string
|
|
25004
25014
|
required:
|
|
@@ -26130,6 +26140,10 @@ components:
|
|
|
26130
26140
|
type: string
|
|
26131
26141
|
appId:
|
|
26132
26142
|
type: string
|
|
26143
|
+
attach:
|
|
26144
|
+
type: array
|
|
26145
|
+
items:
|
|
26146
|
+
$ref: "#/components/schemas/EmailAttachmentSpec"
|
|
26133
26147
|
required:
|
|
26134
26148
|
- contactId
|
|
26135
26149
|
- templateId
|
|
@@ -26149,6 +26163,10 @@ components:
|
|
|
26149
26163
|
- wallet
|
|
26150
26164
|
messageId:
|
|
26151
26165
|
type: string
|
|
26166
|
+
attachmentsLeftOut:
|
|
26167
|
+
type: array
|
|
26168
|
+
items:
|
|
26169
|
+
type: string
|
|
26152
26170
|
required:
|
|
26153
26171
|
- ok
|
|
26154
26172
|
- channel
|
|
@@ -26172,6 +26190,9 @@ components:
|
|
|
26172
26190
|
$ref: "#/components/schemas/CommsSettings"
|
|
26173
26191
|
ConsentChannels:
|
|
26174
26192
|
$ref: "#/components/schemas/BroadcastChannel"
|
|
26193
|
+
EmailAttachmentSpec:
|
|
26194
|
+
type: object
|
|
26195
|
+
additionalProperties: true
|
|
26175
26196
|
TransactionalSendResult:
|
|
26176
26197
|
anyOf:
|
|
26177
26198
|
- $ref: "#/components/schemas/TransactionalSendResponse"
|
|
@@ -27708,6 +27729,9 @@ components:
|
|
|
27708
27729
|
accountData:
|
|
27709
27730
|
type: object
|
|
27710
27731
|
additionalProperties: true
|
|
27732
|
+
autoHeight:
|
|
27733
|
+
type: object
|
|
27734
|
+
additionalProperties: true
|
|
27711
27735
|
required:
|
|
27712
27736
|
- collectionId
|
|
27713
27737
|
- appId
|
package/dist/types/bookings.d.ts
CHANGED
|
@@ -283,6 +283,8 @@ export interface MessageBookersInput {
|
|
|
283
283
|
/** Default: held, pending, confirmed. */
|
|
284
284
|
statuses?: BookingStatus[];
|
|
285
285
|
ref?: string;
|
|
286
|
+
/** Files on each email, as comms.sendTransactional — `'calendar'` is each person's own booking. */
|
|
287
|
+
attach?: import('./comms.js').EmailAttachmentSpec[];
|
|
286
288
|
}
|
|
287
289
|
export interface MessageBookersResult {
|
|
288
290
|
matched: number;
|
|
@@ -34,6 +34,10 @@ export interface BroadcastRecord {
|
|
|
34
34
|
priority?: number;
|
|
35
35
|
}>;
|
|
36
36
|
};
|
|
37
|
+
/** Files on every email of the broadcast (at most 5, 10 MB in all): uploaded assets only. */
|
|
38
|
+
attach?: Array<{
|
|
39
|
+
asset: string;
|
|
40
|
+
}>;
|
|
37
41
|
[key: string]: unknown;
|
|
38
42
|
};
|
|
39
43
|
createdAt: string;
|
package/dist/types/comms.d.ts
CHANGED
|
@@ -355,13 +355,41 @@ export interface TransactionalSendRequest {
|
|
|
355
355
|
ref?: string;
|
|
356
356
|
/** App context stored on the comms-events row */
|
|
357
357
|
appId?: string;
|
|
358
|
+
/**
|
|
359
|
+
* Files to attach (email only; at most 5, 10 MB in all). What can't be made is left out and listed in
|
|
360
|
+
* `attachmentsLeftOut` — the email still goes.
|
|
361
|
+
*/
|
|
362
|
+
attach?: EmailAttachmentSpec[];
|
|
358
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* One file for an email:
|
|
366
|
+
* - `'calendar'` — the booking's `.ics` calendar invite (needs `include.booking` and `appId`)
|
|
367
|
+
* - `{ asset }` — a file uploaded to this collection's assets
|
|
368
|
+
* - `{ function }` — the app's (`appId`) own server function makes it (see server-functions.md, "Making an email attachment")
|
|
369
|
+
* - `{ filename, contentType, content }` — a file you made, base64 (PDF, Office documents, images, text, CSV, calendar)
|
|
370
|
+
* - `{ filename, file }` — a private file you made (e.g. `pdf.create` with `return: 'signedUrl'`)
|
|
371
|
+
*/
|
|
372
|
+
export type EmailAttachmentSpec = 'calendar' | {
|
|
373
|
+
asset: string;
|
|
374
|
+
} | {
|
|
375
|
+
function: string;
|
|
376
|
+
} | {
|
|
377
|
+
filename: string;
|
|
378
|
+
contentType: string;
|
|
379
|
+
content: string;
|
|
380
|
+
} | {
|
|
381
|
+
filename: string;
|
|
382
|
+
contentType?: string;
|
|
383
|
+
file: string;
|
|
384
|
+
};
|
|
359
385
|
export interface TransactionalSendResponse {
|
|
360
386
|
ok: true;
|
|
361
387
|
/** The channel the message was actually sent on */
|
|
362
388
|
channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet';
|
|
363
389
|
/** Provider message ID (email/SMS/WhatsApp); absent for push/wallet */
|
|
364
390
|
messageId?: string;
|
|
391
|
+
/** Attachments that couldn't be made, and why (the email went without them) */
|
|
392
|
+
attachmentsLeftOut?: string[];
|
|
365
393
|
}
|
|
366
394
|
export interface TransactionalSendError {
|
|
367
395
|
ok: false;
|
|
@@ -36,6 +36,21 @@ export interface IframeResponderOptions {
|
|
|
36
36
|
onAuthLogin?: (token: string, user: any, accountData?: Record<string, any>) => Promise<void>;
|
|
37
37
|
onAuthLogout?: () => Promise<void>;
|
|
38
38
|
onRouteChange?: (path: string, state: Record<string, string>) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Size the iframe to the app's content, for an app placed in a page (a booking form in a website, a
|
|
41
|
+
* widget in a section): the page scrolls, never the iframe. The responder sets the iframe's height from
|
|
42
|
+
* the height the app reports, adding the iframe's own border and padding, and never uses the
|
|
43
|
+
* window-filling height. `minHeight`: the least height (px) it's given.
|
|
44
|
+
* Leave off for a full-screen host, which sizes the iframe itself from `onResize`.
|
|
45
|
+
*/
|
|
46
|
+
autoHeight?: boolean | {
|
|
47
|
+
minHeight?: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A height for the iframe: the app's content height whenever the app reports one, and — unless
|
|
51
|
+
* `autoHeight` is on — the window's height below the iframe's container on every window resize (for a
|
|
52
|
+
* full-screen host). A page that embeds an app should use `autoHeight` instead.
|
|
53
|
+
*/
|
|
39
54
|
onResize?: (height: number) => void;
|
|
40
55
|
onError?: (error: Error) => void;
|
|
41
56
|
onReady?: () => void;
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8 | Generated: 2026-11-02T13:05:01.425Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -5402,6 +5402,7 @@ interface MessageBookersInput {
|
|
|
5402
5402
|
resourceId?: string
|
|
5403
5403
|
statuses?: BookingStatus[]
|
|
5404
5404
|
ref?: string
|
|
5405
|
+
attach?: import('./comms').EmailAttachmentSpec[]
|
|
5405
5406
|
}
|
|
5406
5407
|
```
|
|
5407
5408
|
|
|
@@ -5512,6 +5513,7 @@ interface BroadcastRecord {
|
|
|
5512
5513
|
priority?: number
|
|
5513
5514
|
}>
|
|
5514
5515
|
}
|
|
5516
|
+
attach?: Array<{ asset: string }>
|
|
5515
5517
|
[key: string]: unknown
|
|
5516
5518
|
}
|
|
5517
5519
|
createdAt: string
|
|
@@ -6242,6 +6244,7 @@ interface TransactionalSendRequest {
|
|
|
6242
6244
|
}
|
|
6243
6245
|
ref?: string
|
|
6244
6246
|
appId?: string
|
|
6247
|
+
attach?: EmailAttachmentSpec[]
|
|
6245
6248
|
}
|
|
6246
6249
|
```
|
|
6247
6250
|
|
|
@@ -6251,6 +6254,7 @@ interface TransactionalSendResponse {
|
|
|
6251
6254
|
ok: true
|
|
6252
6255
|
channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
|
|
6253
6256
|
messageId?: string
|
|
6257
|
+
attachmentsLeftOut?: string[]
|
|
6254
6258
|
}
|
|
6255
6259
|
```
|
|
6256
6260
|
|
|
@@ -6270,6 +6274,8 @@ interface TransactionalSendError {
|
|
|
6270
6274
|
|
|
6271
6275
|
**ConsentChannels** = `Partial<Record<BroadcastChannel, boolean>>`
|
|
6272
6276
|
|
|
6277
|
+
**EmailAttachmentSpec** = ``
|
|
6278
|
+
|
|
6273
6279
|
**TransactionalSendResult** = `// src/types/comms.ts
|
|
6274
6280
|
// Communication and notification types for the Smartlinks API
|
|
6275
6281
|
import type { IdField } from './common'
|
|
@@ -6595,14 +6601,36 @@ export interface TransactionalSendRequest {
|
|
|
6595
6601
|
ref?: string
|
|
6596
6602
|
/** App context stored on the comms-events row */
|
|
6597
6603
|
appId?: string
|
|
6604
|
+
/**
|
|
6605
|
+
* Files to attach (email only; at most 5, 10 MB in all). What can't be made is left out and listed in
|
|
6606
|
+
* `attachmentsLeftOut` — the email still goes.
|
|
6607
|
+
*/
|
|
6608
|
+
attach?: EmailAttachmentSpec[]
|
|
6598
6609
|
}
|
|
6599
6610
|
|
|
6611
|
+
/**
|
|
6612
|
+
* One file for an email:
|
|
6613
|
+
* - `'calendar'` — the booking's `.ics` calendar invite (needs `include.booking` and `appId`)
|
|
6614
|
+
* - `{ asset }` — a file uploaded to this collection's assets
|
|
6615
|
+
* - `{ function }` — the app's (`appId`) own server function makes it (see server-functions.md, "Making an email attachment")
|
|
6616
|
+
* - `{ filename, contentType, content }` — a file you made, base64 (PDF, Office documents, images, text, CSV, calendar)
|
|
6617
|
+
* - `{ filename, file }` — a private file you made (e.g. `pdf.create` with `return: 'signedUrl'`)
|
|
6618
|
+
*/
|
|
6619
|
+
export type EmailAttachmentSpec =
|
|
6620
|
+
| 'calendar'
|
|
6621
|
+
| { asset: string }
|
|
6622
|
+
| { function: string }
|
|
6623
|
+
| { filename: string; contentType: string; content: string }
|
|
6624
|
+
| { filename: string; contentType?: string; file: string }
|
|
6625
|
+
|
|
6600
6626
|
export interface TransactionalSendResponse {
|
|
6601
6627
|
ok: true
|
|
6602
6628
|
/** The channel the message was actually sent on */
|
|
6603
6629
|
channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
|
|
6604
6630
|
/** Provider message ID (email/SMS/WhatsApp); absent for push/wallet */
|
|
6605
6631
|
messageId?: string
|
|
6632
|
+
/** Attachments that couldn't be made, and why (the email went without them) */
|
|
6633
|
+
attachmentsLeftOut?: string[]
|
|
6606
6634
|
}
|
|
6607
6635
|
|
|
6608
6636
|
export interface TransactionalSendError {
|
|
@@ -7511,6 +7539,7 @@ interface IframeResponderOptions {
|
|
|
7511
7539
|
) => Promise<void>;
|
|
7512
7540
|
onAuthLogout?: () => Promise<void>;
|
|
7513
7541
|
onRouteChange?: (path: string, state: Record<string, string>) => void;
|
|
7542
|
+
autoHeight?: boolean | { minHeight?: number };
|
|
7514
7543
|
onResize?: (height: number) => void;
|
|
7515
7544
|
onError?: (error: Error) => void;
|
|
7516
7545
|
onReady?: () => void;
|
package/docs/bookings.md
CHANGED
|
@@ -368,6 +368,17 @@ wording.
|
|
|
368
368
|
A **public** resource record (a staff profile the booking page lists) keeps its address on a
|
|
369
369
|
**private child record** instead (`visibility: 'admin'`, `parentType: 'record'`, `parentId` = the
|
|
370
370
|
resource) — the platform looks there too, and the address never ships with the public listing.
|
|
371
|
+
- Any step can also take `replyTo` — an address, or `"contact"` (replies go to the person the event is
|
|
372
|
+
about: a team notification answered straight to the booker) — and `cc` / `bcc` (written-out addresses,
|
|
373
|
+
same rules as `to`, counted in the same daily cap; over it the person still gets their email, without the
|
|
374
|
+
copies). A site's `cloud/email.json` (`{ "fromName", "replyTo" }`) sets the name the business's emails come
|
|
375
|
+
from and their default reply-to; the From address is the business's verified domain.
|
|
376
|
+
- `attach` on a step adds files (at most 5, 10 MB in all): `"calendar"` — the booking as an `.ics` invite
|
|
377
|
+
that drops straight into the person's calendar (a cancelled booking's invite cancels it there);
|
|
378
|
+
`{ "asset": "<assetId>" }` — a file in the collection's assets (terms, a price list);
|
|
379
|
+
`{ "function": "<name>" }` — your app's own function makes it (a receipt PDF: server-functions.md,
|
|
380
|
+
"Making an email attachment"). E.g. `{ "send": "booking-confirmed", "to": "contact", "attach": ["calendar"] }`.
|
|
381
|
+
One that can't be made is left out; the email still goes.
|
|
371
382
|
- Who gets what, in templates: `{{ booking }}` is the booking **ready-formatted**: its fields and
|
|
372
383
|
`data`, plus `serviceName`, `staffName`, `customer`, `durationMinutes`, `when` (`date`,
|
|
373
384
|
`shortDate`, `time`, `endTime`, `dateTime`, `timezone` — in the business's timezone) and, when the
|
|
@@ -393,9 +404,14 @@ window (optionally with one resource), each hydrated with **their own** booking.
|
|
|
393
404
|
await SL.bookings.message(collectionId, appId, { templateId, slotId })
|
|
394
405
|
// Everyone booked with Sam next week, by email even if they prefer texts
|
|
395
406
|
await SL.bookings.message(collectionId, appId, { templateId, channel: 'email', from, to, resourceType: 'record', resourceId: samId })
|
|
407
|
+
// Everyone on the class, each with their own calendar invite
|
|
408
|
+
await SL.bookings.message(collectionId, appId, { templateId, slotId, attach: ['calendar'] })
|
|
396
409
|
// → { matched, sent, skipped: { noContact, duplicate, failed }, failures: [{ bookingId, reason }] }
|
|
397
410
|
```
|
|
398
411
|
|
|
412
|
+
`attach` takes the same files as `comms.sendTransactional` (comms.md, "Attachments"); `'calendar'` is
|
|
413
|
+
each person's own booking.
|
|
414
|
+
|
|
399
415
|
Statuses default to held, pending and confirmed; at most 500 recipients per call (narrow the window
|
|
400
416
|
for more). One message per contact on a slot, one per booking in a window. Also
|
|
401
417
|
`ctx.sl.bookings.message(input)` in a server function (`sl:bookings:write`) — a cron function that
|
package/docs/comms.md
CHANGED
|
@@ -56,6 +56,7 @@ if (result.ok) {
|
|
|
56
56
|
| `include` | object | Hydration flags — see table below. |
|
|
57
57
|
| `ref` | `string` | Arbitrary reference string stored with the event. |
|
|
58
58
|
| `appId` | `string` | App identifier, used for context/logging. |
|
|
59
|
+
| `attach` | `EmailAttachmentSpec[]` | Files on the email — see [Attachments](#attachments). |
|
|
59
60
|
|
|
60
61
|
**`include` fields:**
|
|
61
62
|
|
|
@@ -73,12 +74,36 @@ if (result.ok) {
|
|
|
73
74
|
|
|
74
75
|
```typescript
|
|
75
76
|
// Success
|
|
76
|
-
{ ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string }
|
|
77
|
+
{ ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string; attachmentsLeftOut?: string[] }
|
|
77
78
|
|
|
78
79
|
// Failure
|
|
79
80
|
{ ok: false; error: string }
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
### Attachments
|
|
84
|
+
|
|
85
|
+
`attach` puts files on the email (email only — ignored when the send goes by text or push). At most 5
|
|
86
|
+
files and 10 MB in all; PDF, Word / Excel / PowerPoint, images, text, CSV and calendar files.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
await comms.sendTransactional(collectionId, {
|
|
90
|
+
contactId, templateId: 'booking-confirmed', appId: 'appointment-booking',
|
|
91
|
+
include: { booking: bookingId },
|
|
92
|
+
attach: [
|
|
93
|
+
'calendar', // the booking's .ics invite (needs include.booking + appId)
|
|
94
|
+
{ asset: 'a1b2c3' }, // a file uploaded to the collection's assets
|
|
95
|
+
{ function: 'receiptPdf' }, // the app's own server function makes it
|
|
96
|
+
{ filename: 'Quote.pdf', contentType: 'application/pdf', content: base64 }, // a file you made
|
|
97
|
+
{ filename: 'Report.pdf', file: signedUrl }, // a private file you made (pdf.create return: 'signedUrl')
|
|
98
|
+
],
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A malformed list is refused (`transactional.attach_invalid`). A file that can't be made at send time
|
|
103
|
+
(a missing asset, a function that fails) is left out and listed in `attachmentsLeftOut` — the email
|
|
104
|
+
still goes. The request body is capped at 10 MB, so for big files make a private file and pass `file`.
|
|
105
|
+
`bookings.message` takes the same `attach` (`'calendar'` is each person's own booking).
|
|
106
|
+
|
|
82
107
|
---
|
|
83
108
|
|
|
84
109
|
## Broadcasts
|
|
@@ -149,11 +174,12 @@ Consent resolution order (per recipient, per channel):
|
|
|
149
174
|
3. `preferences._default.topics[topic]`
|
|
150
175
|
4. Subject-specific preferences (when hydrating a subject context)
|
|
151
176
|
|
|
152
|
-
Minimal broadcast data shape:
|
|
177
|
+
Minimal broadcast data shape (`attach` is optional — uploaded assets only, the same files on every email):
|
|
153
178
|
```json
|
|
154
179
|
{
|
|
155
180
|
"data": {
|
|
156
181
|
"topic": "newsletter",
|
|
182
|
+
"attach": [{ "asset": "a1b2c3" }],
|
|
157
183
|
"channelSettings": {
|
|
158
184
|
"mode": "preferred",
|
|
159
185
|
"channels": [
|
package/docs/iframe-responder.md
CHANGED
|
@@ -35,10 +35,8 @@ const responder = new smartlinks.IframeResponder({
|
|
|
35
35
|
smartlinks.setBearerToken(token);
|
|
36
36
|
console.log('User logged in:', user);
|
|
37
37
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
iframeElement.style.height = `${height}px`;
|
|
41
|
-
},
|
|
38
|
+
// An app in a page: the iframe is as tall as the app's content — the page scrolls, never the iframe.
|
|
39
|
+
autoHeight: { minHeight: 480 },
|
|
42
40
|
});
|
|
43
41
|
|
|
44
42
|
// Attach to iframe element
|
|
@@ -115,9 +113,24 @@ interface IframeResponderOptions {
|
|
|
115
113
|
onResize?: (height: number) => void;
|
|
116
114
|
onError?: (error: Error) => void;
|
|
117
115
|
onReady?: () => void;
|
|
116
|
+
|
|
117
|
+
// Height
|
|
118
|
+
autoHeight?: boolean | { minHeight?: number }; // size the iframe to the app's content (an app in a page)
|
|
118
119
|
}
|
|
119
120
|
```
|
|
120
121
|
|
|
122
|
+
**Height: `autoHeight` or `onResize`.**
|
|
123
|
+
|
|
124
|
+
- **`autoHeight`** — for an app placed in a page (a website, a section of a dashboard). The responder sets
|
|
125
|
+
the iframe's height from the height the app reports, adds the iframe's own border and padding (an
|
|
126
|
+
element with `box-sizing: border-box`, Tailwind's default, otherwise ends up short by its border — a
|
|
127
|
+
pointless scrollbar), and never uses the window-filling height. The iframe's own scrolling stays on as a
|
|
128
|
+
fallback for an app on an older SDK that under-reports its height. `minHeight`
|
|
129
|
+
is the least height it gives. `onResize` still hears each content height.
|
|
130
|
+
- **`onResize`** without `autoHeight` — for a full-screen host that sizes the iframe itself. It receives
|
|
131
|
+
the app's content height whenever the app reports one, **and** on every window resize the window's
|
|
132
|
+
height below the iframe's container.
|
|
133
|
+
|
|
121
134
|
## Advanced Examples
|
|
122
135
|
|
|
123
136
|
### Deep Linking / Route Synchronization
|
|
@@ -175,6 +188,8 @@ const responder = new smartlinks.IframeResponder({
|
|
|
175
188
|
|
|
176
189
|
### Responsive Height Management
|
|
177
190
|
|
|
191
|
+
An app in a page: `autoHeight` (above) does it all. A full-screen host that manages the height itself:
|
|
192
|
+
|
|
178
193
|
```typescript
|
|
179
194
|
const responder = new smartlinks.IframeResponder({
|
|
180
195
|
collectionId: 'acme-wines',
|
|
@@ -442,7 +457,7 @@ import type {
|
|
|
442
457
|
3. **Clean up** responder with `destroy()` when unmounting
|
|
443
458
|
4. **Use version control** to test against development versions
|
|
444
459
|
5. **Validate tokens** server-side for security
|
|
445
|
-
6. **
|
|
460
|
+
6. **Size the iframe to its content** with `autoHeight` (an app in a page) or `onResize` (a full-screen host)
|
|
446
461
|
7. **Sync routes** with parent navigation for better UX
|
|
447
462
|
8. **Set `parentPath`** for routes that have a meaningful parent screen in the app hierarchy
|
|
448
463
|
|
|
@@ -458,8 +473,9 @@ import type {
|
|
|
458
473
|
- Verify token is being set with `setBearerToken()`
|
|
459
474
|
- Check that auth messages are being received
|
|
460
475
|
|
|
461
|
-
### Height not updating
|
|
462
|
-
-
|
|
476
|
+
### Height not updating, or a scrollbar inside the iframe
|
|
477
|
+
- An app in a page: use `autoHeight` instead of setting the height yourself
|
|
478
|
+
- A full-screen host: implement the `onResize` callback
|
|
463
479
|
- Ensure iframe has CSS styling for height changes
|
|
464
480
|
- Check that iframe is sending resize messages
|
|
465
481
|
|
package/docs/server-functions.md
CHANGED
|
@@ -703,6 +703,35 @@ export async function printLabel(ctx, event) {
|
|
|
703
703
|
- **Verify:** `pdf.decodeBarcodes` on a `signedUrl` reads the codes back deterministically, so you can
|
|
704
704
|
confirm the digits before answering.
|
|
705
705
|
|
|
706
|
+
### Making an email attachment
|
|
707
|
+
|
|
708
|
+
A flow step can attach a file your function makes: `"attach": [{ "function": "receiptPdf" }]` in a
|
|
709
|
+
site's `cloud/flows.json` (or an app's blueprint). When the email goes, the platform calls your function
|
|
710
|
+
(your app's, in this collection, as the collection, like a connector step) with:
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
event.body = { purpose: 'attachment', eventType, interactionId, outcome, contactId, metadata, booking? } // booking: ready-formatted
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
and attaches what it returns:
|
|
717
|
+
|
|
718
|
+
```ts
|
|
719
|
+
// capabilities: ["tools:pdf.create"]
|
|
720
|
+
export async function receiptPdf(ctx, event) {
|
|
721
|
+
const b = event.body.booking
|
|
722
|
+
const pdf = await ctx.sl.tools.run('pdf.create', { format: 'A4', html: `<h1>Receipt</h1><p>${b.serviceName}, ${b.when.dateTime}</p>`, return: 'signedUrl' })
|
|
723
|
+
return { filename: `receipt-${b.id}.pdf`, file: pdf.url } // a private file you made
|
|
724
|
+
// or { filename, contentType: 'application/pdf', content: '<base64>' }, a list of those, or null for none
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
- Files: PDF, calendar (`.ics`), images, text / CSV and Office documents; at most 5 per email and 10 MB
|
|
729
|
+
in all. A private file must be one this platform made (`return: 'signedUrl'`), never an outside URL.
|
|
730
|
+
- If your function fails or returns something else, the email still goes without it (the reason is in
|
|
731
|
+
your function's runs).
|
|
732
|
+
- Other sources need no code: `"calendar"` (a booking event's calendar invite) and
|
|
733
|
+
`{ "asset": "<assetId>" }` (a file in the collection's assets) — see bookings.md, "Flows on booking events".
|
|
734
|
+
|
|
706
735
|
---
|
|
707
736
|
|
|
708
737
|
## Worked example — a public competition entry
|
package/docs/websites.md
CHANGED
|
@@ -80,11 +80,17 @@ const responder = new SL.IframeResponder({
|
|
|
80
80
|
appId: 'appointments', // the app's id on this collection
|
|
81
81
|
initialPath: '/my-bookings', // the page's `path`
|
|
82
82
|
params: { service: 'svc_123' }, // what the page `accepts`
|
|
83
|
-
|
|
83
|
+
autoHeight: { minHeight: 480 }, // the iframe is as tall as the page: your page scrolls, never the iframe
|
|
84
84
|
})
|
|
85
85
|
iframe.src = await responder.attach(iframe)
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
- **Size it with `autoHeight`, not by hand.** The responder sets the iframe's height from the page's own
|
|
89
|
+
height and counts the iframe's border and padding, so there's nothing left to scroll. Setting
|
|
90
|
+
`iframe.style.height` from `onResize` yourself goes wrong in a site: `onResize` also fires on every
|
|
91
|
+
window resize with "the window below the iframe" (for a full-screen host), and a bordered iframe
|
|
92
|
+
(Tailwind's border-box) ends up a few pixels short — a scrollbar inside the frame.
|
|
93
|
+
|
|
88
94
|
- **The visitor's session goes with it.** The page's API calls are proxied through the site, so it
|
|
89
95
|
acts as whoever is signed in to the site (or as a guest). Never pass a token in the URL.
|
|
90
96
|
- **The app must be installed** on the site's collection; its address comes from there.
|
package/openapi.yaml
CHANGED
|
@@ -24805,6 +24805,11 @@ components:
|
|
|
24805
24805
|
$ref: "#/components/schemas/BookingStatus"
|
|
24806
24806
|
ref:
|
|
24807
24807
|
type: string
|
|
24808
|
+
attach:
|
|
24809
|
+
type: array
|
|
24810
|
+
items:
|
|
24811
|
+
type: object
|
|
24812
|
+
additionalProperties: true
|
|
24808
24813
|
required:
|
|
24809
24814
|
- templateId
|
|
24810
24815
|
MessageBookersResult:
|
|
@@ -24999,6 +25004,11 @@ components:
|
|
|
24999
25004
|
type: boolean
|
|
25000
25005
|
priority:
|
|
25001
25006
|
type: number
|
|
25007
|
+
attach:
|
|
25008
|
+
type: array
|
|
25009
|
+
items:
|
|
25010
|
+
type: object
|
|
25011
|
+
additionalProperties: true
|
|
25002
25012
|
createdAt:
|
|
25003
25013
|
type: string
|
|
25004
25014
|
required:
|
|
@@ -26130,6 +26140,10 @@ components:
|
|
|
26130
26140
|
type: string
|
|
26131
26141
|
appId:
|
|
26132
26142
|
type: string
|
|
26143
|
+
attach:
|
|
26144
|
+
type: array
|
|
26145
|
+
items:
|
|
26146
|
+
$ref: "#/components/schemas/EmailAttachmentSpec"
|
|
26133
26147
|
required:
|
|
26134
26148
|
- contactId
|
|
26135
26149
|
- templateId
|
|
@@ -26149,6 +26163,10 @@ components:
|
|
|
26149
26163
|
- wallet
|
|
26150
26164
|
messageId:
|
|
26151
26165
|
type: string
|
|
26166
|
+
attachmentsLeftOut:
|
|
26167
|
+
type: array
|
|
26168
|
+
items:
|
|
26169
|
+
type: string
|
|
26152
26170
|
required:
|
|
26153
26171
|
- ok
|
|
26154
26172
|
- channel
|
|
@@ -26172,6 +26190,9 @@ components:
|
|
|
26172
26190
|
$ref: "#/components/schemas/CommsSettings"
|
|
26173
26191
|
ConsentChannels:
|
|
26174
26192
|
$ref: "#/components/schemas/BroadcastChannel"
|
|
26193
|
+
EmailAttachmentSpec:
|
|
26194
|
+
type: object
|
|
26195
|
+
additionalProperties: true
|
|
26175
26196
|
TransactionalSendResult:
|
|
26176
26197
|
anyOf:
|
|
26177
26198
|
- $ref: "#/components/schemas/TransactionalSendResponse"
|
|
@@ -27708,6 +27729,9 @@ components:
|
|
|
27708
27729
|
accountData:
|
|
27709
27730
|
type: object
|
|
27710
27731
|
additionalProperties: true
|
|
27732
|
+
autoHeight:
|
|
27733
|
+
type: object
|
|
27734
|
+
additionalProperties: true
|
|
27711
27735
|
required:
|
|
27712
27736
|
- collectionId
|
|
27713
27737
|
- appId
|
package/package.json
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@proveanything/smartlinks",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js",
|
|
12
|
-
"default": "./dist/index.js"
|
|
13
|
-
},
|
|
14
|
-
"./testing": {
|
|
15
|
-
"types": "./dist/testing/index.d.ts",
|
|
16
|
-
"import": "./dist/testing/index.js",
|
|
17
|
-
"default": "./dist/testing/index.js"
|
|
18
|
-
},
|
|
19
|
-
"./*": {
|
|
20
|
-
"types": "./dist/*.d.ts",
|
|
21
|
-
"import": "./dist/*.js",
|
|
22
|
-
"default": "./dist/*.js"
|
|
23
|
-
},
|
|
24
|
-
"./baseline.css": "./dist/baseline.css",
|
|
25
|
-
"./baseline.classes.json": "./dist/baseline.classes.json",
|
|
26
|
-
"./theme.css": "./dist/theme.css",
|
|
27
|
-
"./theme-boot.js": "./dist/theme-boot.js",
|
|
28
|
-
"./package.json": "./package.json"
|
|
29
|
-
},
|
|
30
|
-
"bin": {
|
|
31
|
-
"smartlinks-register-release": "scripts/register-release.mjs",
|
|
32
|
-
"smartlinks-publish": "scripts/publish.mjs",
|
|
33
|
-
"smartlinks-doctor": "scripts/doctor.mjs",
|
|
34
|
-
"smartlinks-headless": "scripts/headless-check.mjs"
|
|
35
|
-
},
|
|
36
|
-
"files": [
|
|
37
|
-
"dist/",
|
|
38
|
-
"docs/",
|
|
39
|
-
"scripts/register-release.mjs",
|
|
40
|
-
"scripts/publish.mjs",
|
|
41
|
-
"scripts/doctor.mjs",
|
|
42
|
-
"scripts/headless-check.mjs",
|
|
43
|
-
"scripts/lib/",
|
|
44
|
-
"openapi.yaml",
|
|
45
|
-
"README.md"
|
|
46
|
-
],
|
|
47
|
-
"scripts": {
|
|
48
|
-
"test": "node test/run.cjs",
|
|
49
|
-
"build": "tsc && node scripts/fix-esm-extensions.mjs && node generate-api-summary.cjs && node generate-openapi.cjs && node scripts/copy-docs-to-dist.cjs && node scripts/build-baseline.mjs",
|
|
50
|
-
"docs": "typedoc",
|
|
51
|
-
"docs:summary": "node generate-api-summary.cjs",
|
|
52
|
-
"docs:openapi": "node generate-openapi.cjs",
|
|
53
|
-
"docs:openapi:lint": "npx --yes @redocly/cli@latest lint openapi.yaml",
|
|
54
|
-
"docs:openapi:check": "node generate-openapi.cjs && npx --yes @redocly/cli@latest lint openapi.yaml",
|
|
55
|
-
"build:docs": "tsc build-docs.ts --outDir dist && node dist/build-docs.js",
|
|
56
|
-
"prepublishOnly": "npm run build"
|
|
57
|
-
},
|
|
58
|
-
"keywords": [
|
|
59
|
-
"smartlinks",
|
|
60
|
-
"api",
|
|
61
|
-
"authentication",
|
|
62
|
-
"traceability",
|
|
63
|
-
"blockchain",
|
|
64
|
-
"typescript",
|
|
65
|
-
"sdk"
|
|
66
|
-
],
|
|
67
|
-
"author": "Glenn Shoosmith",
|
|
68
|
-
"license": "MIT",
|
|
69
|
-
"publishConfig": {
|
|
70
|
-
"access": "public"
|
|
71
|
-
},
|
|
72
|
-
"dependencies": {
|
|
73
|
-
"cross-fetch": "^3.1.5",
|
|
74
|
-
"es-module-lexer": "^1.7.0"
|
|
75
|
-
},
|
|
76
|
-
"devDependencies": {
|
|
77
|
-
"ts-node": "^10.9.1",
|
|
78
|
-
"typedoc": "^0.25.13",
|
|
79
|
-
"typedoc-plugin-markdown": "^3.17.1",
|
|
80
|
-
"typescript": "^4.9.0"
|
|
81
|
-
},
|
|
82
|
-
"repository": {
|
|
83
|
-
"type": "git",
|
|
84
|
-
"url": "https://github.com/Prove-Anything/smartlinks.git"
|
|
85
|
-
},
|
|
86
|
-
"homepage": "https://smartlinks.app"
|
|
87
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@proveanything/smartlinks",
|
|
3
|
+
"version": "2.1.8",
|
|
4
|
+
"description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./testing": {
|
|
15
|
+
"types": "./dist/testing/index.d.ts",
|
|
16
|
+
"import": "./dist/testing/index.js",
|
|
17
|
+
"default": "./dist/testing/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./*": {
|
|
20
|
+
"types": "./dist/*.d.ts",
|
|
21
|
+
"import": "./dist/*.js",
|
|
22
|
+
"default": "./dist/*.js"
|
|
23
|
+
},
|
|
24
|
+
"./baseline.css": "./dist/baseline.css",
|
|
25
|
+
"./baseline.classes.json": "./dist/baseline.classes.json",
|
|
26
|
+
"./theme.css": "./dist/theme.css",
|
|
27
|
+
"./theme-boot.js": "./dist/theme-boot.js",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"smartlinks-register-release": "scripts/register-release.mjs",
|
|
32
|
+
"smartlinks-publish": "scripts/publish.mjs",
|
|
33
|
+
"smartlinks-doctor": "scripts/doctor.mjs",
|
|
34
|
+
"smartlinks-headless": "scripts/headless-check.mjs"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist/",
|
|
38
|
+
"docs/",
|
|
39
|
+
"scripts/register-release.mjs",
|
|
40
|
+
"scripts/publish.mjs",
|
|
41
|
+
"scripts/doctor.mjs",
|
|
42
|
+
"scripts/headless-check.mjs",
|
|
43
|
+
"scripts/lib/",
|
|
44
|
+
"openapi.yaml",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "node test/run.cjs",
|
|
49
|
+
"build": "tsc && node scripts/fix-esm-extensions.mjs && node generate-api-summary.cjs && node generate-openapi.cjs && node scripts/copy-docs-to-dist.cjs && node scripts/build-baseline.mjs",
|
|
50
|
+
"docs": "typedoc",
|
|
51
|
+
"docs:summary": "node generate-api-summary.cjs",
|
|
52
|
+
"docs:openapi": "node generate-openapi.cjs",
|
|
53
|
+
"docs:openapi:lint": "npx --yes @redocly/cli@latest lint openapi.yaml",
|
|
54
|
+
"docs:openapi:check": "node generate-openapi.cjs && npx --yes @redocly/cli@latest lint openapi.yaml",
|
|
55
|
+
"build:docs": "tsc build-docs.ts --outDir dist && node dist/build-docs.js",
|
|
56
|
+
"prepublishOnly": "npm run build"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"smartlinks",
|
|
60
|
+
"api",
|
|
61
|
+
"authentication",
|
|
62
|
+
"traceability",
|
|
63
|
+
"blockchain",
|
|
64
|
+
"typescript",
|
|
65
|
+
"sdk"
|
|
66
|
+
],
|
|
67
|
+
"author": "Glenn Shoosmith",
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"cross-fetch": "^3.1.5",
|
|
74
|
+
"es-module-lexer": "^1.7.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"ts-node": "^10.9.1",
|
|
78
|
+
"typedoc": "^0.25.13",
|
|
79
|
+
"typedoc-plugin-markdown": "^3.17.1",
|
|
80
|
+
"typescript": "^4.9.0"
|
|
81
|
+
},
|
|
82
|
+
"repository": {
|
|
83
|
+
"type": "git",
|
|
84
|
+
"url": "https://github.com/Prove-Anything/smartlinks.git"
|
|
85
|
+
},
|
|
86
|
+
"homepage": "https://smartlinks.app"
|
|
87
|
+
}
|