@sentroy-co/client-sdk 2.6.2 → 2.6.4
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/AGENTS.md +692 -0
- package/README.md +53 -634
- package/dist/resources/media.d.ts +28 -0
- package/dist/resources/media.d.ts.map +1 -1
- package/dist/resources/media.js +35 -0
- package/dist/resources/media.js.map +1 -1
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/resources/media.ts +33 -0
- package/src/types.ts +70 -0
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
<img src="https://sentroy.com/business/sentroy-logo-light.png" alt="Sentroy" width="240" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h3 align="center">Sentroy Client SDK
|
|
5
|
+
<h3 align="center">Sentroy Client SDK</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
TypeScript SDK
|
|
9
|
-
|
|
8
|
+
Official TypeScript SDK for the <a href="https://sentroy.com">Sentroy</a> business mail & storage platform.<br />
|
|
9
|
+
Send transactional and bulk email, manage domains and mailboxes, run an inbox, store and serve media — from one typed client.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -15,667 +15,86 @@
|
|
|
15
15
|
<a href="https://github.com/Sentroy-Co/client-sdk/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@sentroy-co/client-sdk.svg" alt="license" /></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://docs.sentroy.com"><strong>Documentation</strong></a>
|
|
20
|
+
·
|
|
21
|
+
<a href="https://docs.sentroy.com/mail">Mail</a>
|
|
22
|
+
·
|
|
23
|
+
<a href="https://docs.sentroy.com/storage">Storage</a>
|
|
24
|
+
·
|
|
25
|
+
<a href="https://docs.sentroy.com/react">React components</a>
|
|
26
|
+
·
|
|
27
|
+
<a href="https://status.sentroy.com">Status</a>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
18
30
|
---
|
|
19
31
|
|
|
20
|
-
##
|
|
32
|
+
## What's in the box
|
|
33
|
+
|
|
34
|
+
- **Mail** — verified domains, mailboxes, multi-language templates, IMAP-backed inbox, transactional and bulk send, suppressions, webhooks, audience lists, deliverability logs.
|
|
35
|
+
- **Storage** — isolated buckets, multipart uploads, image and media transformations, signed download URLs.
|
|
36
|
+
- **React drop-ins** — `<MediaManager />` and `<MediaManagerTrigger />` for instant uploaders, no glue code (`@sentroy-co/client-sdk/react`).
|
|
37
|
+
- **One client, two backends** — point at `https://sentroy.com` for the hosted platform, or your own deployment for self-hosted. Same API, same types.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
21
40
|
|
|
22
41
|
```bash
|
|
23
42
|
npm install @sentroy-co/client-sdk
|
|
24
43
|
```
|
|
25
44
|
|
|
26
|
-
##
|
|
45
|
+
## First request
|
|
27
46
|
|
|
28
47
|
```ts
|
|
29
48
|
import { Sentroy } from "@sentroy-co/client-sdk"
|
|
30
49
|
|
|
31
50
|
const sentroy = new Sentroy({
|
|
32
|
-
baseUrl: "https://sentroy.com",
|
|
51
|
+
baseUrl: "https://sentroy.com", // or your self-hosted URL
|
|
33
52
|
companySlug: "my-company",
|
|
34
|
-
accessToken: "stk_...",
|
|
35
|
-
})
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
> Access tokens can be created from **Admin > Access Tokens** in the Sentroy dashboard.
|
|
39
|
-
|
|
40
|
-
## Usage
|
|
41
|
-
|
|
42
|
-
### Domains
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
// List all domains
|
|
46
|
-
const domains = await sentroy.domains.list()
|
|
47
|
-
|
|
48
|
-
// Get a single domain
|
|
49
|
-
const domain = await sentroy.domains.get("domain-id")
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Mailboxes
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
// List all mailbox accounts
|
|
56
|
-
const mailboxes = await sentroy.mailboxes.list()
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Templates
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
// List all templates
|
|
63
|
-
const templates = await sentroy.templates.list()
|
|
64
|
-
|
|
65
|
-
// Get a template by ID
|
|
66
|
-
const template = await sentroy.templates.get("template-id")
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Templates support multiple languages via `LocalizedString`. A field can be a plain string or an object keyed by language code:
|
|
70
|
-
|
|
71
|
-
```jsonc
|
|
72
|
-
// Example template response
|
|
73
|
-
{
|
|
74
|
-
"id": "b3f1a2c4-...",
|
|
75
|
-
"name": { "en": "Welcome Email", "tr": "Hosgeldin E-postasi" },
|
|
76
|
-
"subject": { "en": "Welcome, {{name}}!", "tr": "Hosgeldin, {{name}}!" },
|
|
77
|
-
"mjmlBody": { "en": "<mjml>...</mjml>", "tr": "<mjml>...</mjml>" },
|
|
78
|
-
"variables": ["name", "company"],
|
|
79
|
-
"domainId": "a1b2c3d4-...",
|
|
80
|
-
"domainName": "example.com",
|
|
81
|
-
"createdAt": "2026-01-15T10:30:00.000Z",
|
|
82
|
-
"updatedAt": "2026-04-10T14:22:00.000Z"
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Use the `variables` array to know which placeholders (`{{name}}`, `{{company}}`) the template expects.
|
|
87
|
-
|
|
88
|
-
### Inbox
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
// List messages
|
|
92
|
-
const messages = await sentroy.inbox.list({
|
|
93
|
-
mailbox: "info@example.com",
|
|
94
|
-
folder: "INBOX",
|
|
95
|
-
page: 1,
|
|
96
|
-
limit: 20,
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
// Get a single message
|
|
100
|
-
const message = await sentroy.inbox.get(1234, {
|
|
101
|
-
mailbox: "info@example.com",
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// List IMAP folders
|
|
105
|
-
const folders = await sentroy.inbox.listFolders("info@example.com")
|
|
106
|
-
|
|
107
|
-
// Get a thread by subject
|
|
108
|
-
const thread = await sentroy.inbox.getThread("Re: Project update", "info@example.com")
|
|
109
|
-
|
|
110
|
-
// Mark as read / unread
|
|
111
|
-
await sentroy.inbox.markAsRead(1234, { mailbox: "info@example.com" })
|
|
112
|
-
await sentroy.inbox.markAsUnread(1234, { mailbox: "info@example.com" })
|
|
113
|
-
|
|
114
|
-
// Move message
|
|
115
|
-
await sentroy.inbox.move(1234, "Trash", {
|
|
116
|
-
from: "INBOX",
|
|
117
|
-
mailbox: "info@example.com",
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// Delete message
|
|
121
|
-
await sentroy.inbox.delete(1234, { mailbox: "info@example.com" })
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Audience
|
|
125
|
-
|
|
126
|
-
Manage contacts and audience lists from the SDK. Useful for building
|
|
127
|
-
your own newsletter signup form, syncing customers from another system,
|
|
128
|
-
or assembling segments for a campaign.
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
// List + paginate contacts (filter by status / tags)
|
|
132
|
-
const { contacts, total, page, limit } = await sentroy.audience.contacts.list({
|
|
133
|
-
page: 1,
|
|
134
|
-
limit: 50,
|
|
135
|
-
status: "active",
|
|
136
|
-
tags: ["customer", "vip"],
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
// Email-prefix autocomplete (capped at 10 server-side)
|
|
140
|
-
const matches = await sentroy.audience.contacts.search("alex@")
|
|
141
|
-
|
|
142
|
-
// Create a contact
|
|
143
|
-
const contact = await sentroy.audience.contacts.create({
|
|
144
|
-
email: "user@example.com",
|
|
145
|
-
name: "Jane Doe",
|
|
146
|
-
tags: ["beta-tester"],
|
|
147
|
-
metadata: { signupSource: "landing-2026-q2" },
|
|
53
|
+
accessToken: "stk_...", // Dashboard → Admin → Access Tokens
|
|
148
54
|
})
|
|
149
55
|
|
|
150
|
-
//
|
|
151
|
-
await sentroy.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
Audience lists are simple groupings; a single contact can belong to many.
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
// CRUD audience lists
|
|
161
|
-
const lists = await sentroy.audience.lists.list()
|
|
162
|
-
const list = await sentroy.audience.lists.create({
|
|
163
|
-
name: "Newsletter — May 2026",
|
|
164
|
-
description: "Opt-ins from the homepage form",
|
|
56
|
+
// Send your first transactional email
|
|
57
|
+
await sentroy.send.email({
|
|
58
|
+
from: "noreply@yourdomain.com",
|
|
59
|
+
to: ["customer@example.com"],
|
|
60
|
+
subject: "Welcome to Acme",
|
|
61
|
+
html: "<p>Glad you're here.</p>",
|
|
165
62
|
})
|
|
166
|
-
await sentroy.audience.lists.delete(list.id)
|
|
167
|
-
|
|
168
|
-
// Membership — scoped to a single list id
|
|
169
|
-
const members = sentroy.audience.lists.members(list.id)
|
|
170
|
-
await members.add(contact.id)
|
|
171
|
-
const inList = await members.list()
|
|
172
|
-
await members.remove(contact.id)
|
|
173
63
|
```
|
|
174
64
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
Suppressed addresses are skipped at send time. Bounces and complaints
|
|
178
|
-
are added automatically; the API is for manually honoring off-platform
|
|
179
|
-
opt-outs or removing a stale entry.
|
|
65
|
+
## Upload a file
|
|
180
66
|
|
|
181
67
|
```ts
|
|
182
|
-
const
|
|
183
|
-
domainId: "domain-id",
|
|
184
|
-
reason: "complaint",
|
|
185
|
-
page: 1,
|
|
186
|
-
limit: 50,
|
|
187
|
-
})
|
|
68
|
+
const file = new File([blob], "invoice.pdf", { type: "application/pdf" })
|
|
188
69
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
reason: "manual",
|
|
70
|
+
const media = await sentroy.media.upload({
|
|
71
|
+
bucketSlug: "invoices",
|
|
72
|
+
file,
|
|
193
73
|
})
|
|
194
74
|
|
|
195
|
-
|
|
75
|
+
console.log(media.url) // signed URL, served from the CDN
|
|
196
76
|
```
|
|
197
77
|
|
|
198
|
-
|
|
78
|
+
That's the smallest useful surface. Every other resource (`domains`, `mailboxes`, `templates`, `inbox`, `audience`, `webhooks`, `suppressions`, `logs`, `buckets`, `media`) follows the same `sentroy.<resource>.<verb>(...)` shape with full TypeScript types.
|
|
199
79
|
|
|
200
|
-
|
|
201
|
-
returned at create time signs every delivery — store it and verify the
|
|
202
|
-
HMAC on your endpoint.
|
|
80
|
+
## Self-hosted vs hosted
|
|
203
81
|
|
|
204
|
-
|
|
205
|
-
const webhook = await sentroy.webhooks.create({
|
|
206
|
-
url: "https://example.com/webhooks/sentroy",
|
|
207
|
-
events: ["sent", "bounced", "opened", "clicked", "unsubscribed"],
|
|
208
|
-
domainId: "domain-id",
|
|
209
|
-
})
|
|
210
|
-
console.log(webhook.secret) // Returned ONCE — store it now
|
|
82
|
+
The SDK is identical in both modes. Only `baseUrl` changes:
|
|
211
83
|
|
|
212
|
-
|
|
213
|
-
|
|
84
|
+
| Mode | `baseUrl` |
|
|
85
|
+
|---|---|
|
|
86
|
+
| Sentroy Cloud (hosted) | `https://sentroy.com` |
|
|
87
|
+
| Self-hosted | `https://your-sentroy-host` |
|
|
214
88
|
|
|
215
|
-
|
|
216
|
-
await sentroy.webhooks.delete(webhook.id)
|
|
217
|
-
```
|
|
89
|
+
Pick the deployment that fits your compliance, latency and cost requirements. Migrate either direction without changing application code.
|
|
218
90
|
|
|
219
|
-
|
|
91
|
+
## Documentation
|
|
220
92
|
|
|
221
|
-
|
|
222
|
-
in your own UI, or build a customer-facing activity timeline.
|
|
93
|
+
Full reference, interactive examples, and multi-language code samples (TypeScript, Go, Python, PHP, cURL) live at:
|
|
223
94
|
|
|
224
|
-
|
|
225
|
-
const logs = await sentroy.logs.list({
|
|
226
|
-
status: "bounced",
|
|
227
|
-
domainId: "domain-id",
|
|
228
|
-
from: "2026-05-01T00:00:00Z",
|
|
229
|
-
to: "2026-05-31T23:59:59Z",
|
|
230
|
-
page: 1,
|
|
231
|
-
limit: 100,
|
|
232
|
-
})
|
|
95
|
+
**[docs.sentroy.com](https://docs.sentroy.com)**
|
|
233
96
|
|
|
234
|
-
|
|
235
|
-
console.log(log.openedAt, log.clickedAt) // tracking timestamps if enabled
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Send Email
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
// Send with a template (uses default language)
|
|
242
|
-
const result = await sentroy.send.email({
|
|
243
|
-
to: "user@example.com",
|
|
244
|
-
from: "info@example.com",
|
|
245
|
-
subject: "Welcome!",
|
|
246
|
-
domainId: "domain-id",
|
|
247
|
-
templateId: "template-id",
|
|
248
|
-
variables: {
|
|
249
|
-
name: "John",
|
|
250
|
-
company: "Acme",
|
|
251
|
-
},
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
// Send with a specific language
|
|
255
|
-
const result = await sentroy.send.email({
|
|
256
|
-
to: "user@example.com",
|
|
257
|
-
from: "info@example.com",
|
|
258
|
-
subject: "Hosgeldin!",
|
|
259
|
-
domainId: "domain-id",
|
|
260
|
-
templateId: "template-id",
|
|
261
|
-
lang: "tr",
|
|
262
|
-
variables: { name: "Ahmet" },
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
// Send with raw HTML
|
|
266
|
-
const result = await sentroy.send.email({
|
|
267
|
-
to: ["user1@example.com", "user2@example.com"],
|
|
268
|
-
from: "info@example.com",
|
|
269
|
-
subject: "Hello",
|
|
270
|
-
domainId: "domain-id",
|
|
271
|
-
html: "<h1>Hello World</h1>",
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
// Send with attachments
|
|
275
|
-
const result = await sentroy.send.email({
|
|
276
|
-
to: "user@example.com",
|
|
277
|
-
from: "info@example.com",
|
|
278
|
-
subject: "Invoice",
|
|
279
|
-
domainId: "domain-id",
|
|
280
|
-
html: "<p>Please find your invoice attached.</p>",
|
|
281
|
-
attachments: [
|
|
282
|
-
{
|
|
283
|
-
filename: "invoice.pdf",
|
|
284
|
-
content: base64String,
|
|
285
|
-
contentType: "application/pdf",
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
})
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Buckets
|
|
292
|
-
|
|
293
|
-
Storage is organized into **buckets** — isolated containers with their own
|
|
294
|
-
visibility (public vs private) and usage counters.
|
|
295
|
-
|
|
296
|
-
```ts
|
|
297
|
-
// List all buckets in the company
|
|
298
|
-
const buckets = await sentroy.buckets.list()
|
|
299
|
-
|
|
300
|
-
// Get a single bucket by its slug
|
|
301
|
-
const bucket = await sentroy.buckets.get("product-assets")
|
|
302
|
-
|
|
303
|
-
// Create a bucket (slug auto-derived from name if omitted)
|
|
304
|
-
const created = await sentroy.buckets.create({
|
|
305
|
-
name: "User Uploads",
|
|
306
|
-
description: "Avatars and profile media",
|
|
307
|
-
isPublic: false,
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
// Update a bucket — toggling isPublic cascades to every file's ACL
|
|
311
|
-
await sentroy.buckets.update("product-assets", { isPublic: true })
|
|
312
|
-
|
|
313
|
-
// Delete a bucket (409 if it has files; use force to purge everything)
|
|
314
|
-
await sentroy.buckets.delete("product-assets", { force: true })
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Media
|
|
318
|
-
|
|
319
|
-
Upload, list, download, and delete files inside a bucket. The same token
|
|
320
|
-
that authorizes mail calls also authorizes storage calls.
|
|
321
|
-
|
|
322
|
-
```ts
|
|
323
|
-
// List files in a bucket
|
|
324
|
-
const { items, total } = await sentroy.media.list("product-assets", {
|
|
325
|
-
type: "image",
|
|
326
|
-
limit: 50,
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
// Get a single media record
|
|
330
|
-
const media = await sentroy.media.get("product-assets", mediaId)
|
|
331
|
-
|
|
332
|
-
// Upload — browser (File from <input>)
|
|
333
|
-
const input = document.querySelector<HTMLInputElement>("input[type=file]")!
|
|
334
|
-
const file = input.files![0]
|
|
335
|
-
const uploaded = await sentroy.media.upload("product-assets", {
|
|
336
|
-
body: file,
|
|
337
|
-
folder: "products",
|
|
338
|
-
tags: ["v1", "cover"],
|
|
339
|
-
})
|
|
340
|
-
console.log(uploaded.url) // Public URL from the CDN
|
|
341
|
-
|
|
342
|
-
// Upload — Node.js (Blob from fs)
|
|
343
|
-
import { openAsBlob } from "node:fs"
|
|
344
|
-
const blob = await openAsBlob("./photo.jpg")
|
|
345
|
-
const uploaded = await sentroy.media.upload("product-assets", {
|
|
346
|
-
body: blob,
|
|
347
|
-
filename: "photo.jpg",
|
|
348
|
-
isPublic: true,
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
// Download — streams from the storage backend; works for both public
|
|
352
|
-
// and private buckets (auth-gated for private).
|
|
353
|
-
const blob = await sentroy.media.download("product-assets", mediaId)
|
|
354
|
-
// Variant: ask for a pre-generated thumbnail width (falls back to
|
|
355
|
-
// original if that size wasn't generated for this file).
|
|
356
|
-
const thumb = await sentroy.media.download("product-assets", mediaId, {
|
|
357
|
-
quality: 500,
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
// Delete — removes S3 objects (original + thumbnails) + Media record
|
|
361
|
-
await sentroy.media.delete("product-assets", mediaId)
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
#### Thumbnail URL helpers
|
|
365
|
-
|
|
366
|
-
When you upload an image, the CDN auto-generates several thumbnail
|
|
367
|
-
sizes (`media.imageMeta.thumbnails`). Showing the original 4000-px JPG
|
|
368
|
-
in a 56-px avatar wastes bandwidth and slows render. Use these helpers
|
|
369
|
-
to pick the right URL for the display target:
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
import {
|
|
373
|
-
pickThumbnailUrl,
|
|
374
|
-
pickPresetThumbnailUrl,
|
|
375
|
-
THUMBNAIL_PRESETS,
|
|
376
|
-
} from "@sentroy-co/client-sdk"
|
|
377
|
-
|
|
378
|
-
// Manual target (px) — pass display size * 2 for retina
|
|
379
|
-
const avatarUrl = pickThumbnailUrl(media, 56 * 2)
|
|
380
|
-
|
|
381
|
-
// Semantic preset — avatar / card / preview / hero
|
|
382
|
-
const cardUrl = pickPresetThumbnailUrl(media, "card") // → ~500px
|
|
383
|
-
const previewUrl = pickPresetThumbnailUrl(media, "preview") // → ~960px
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
The helper picks the smallest thumbnail that still **covers** the
|
|
387
|
-
target (so you never upscale), then falls back through:
|
|
388
|
-
|
|
389
|
-
1. `thumbnail.url` if the backend exposed it directly,
|
|
390
|
-
2. CDN-prefix + `thumbnail.fileName` derived from `media.url`,
|
|
391
|
-
3. proxy `media.downloadUrl?quality=N` for private buckets,
|
|
392
|
-
4. `media.url` / `media.downloadUrl` if no thumbnails exist
|
|
393
|
-
(non-image, or image upload before thumbnails were generated).
|
|
394
|
-
|
|
395
|
-
Returns `undefined` only when the media has no public URL at all.
|
|
396
|
-
|
|
397
|
-
| Preset | Target px | Use case |
|
|
398
|
-
|-------------|-----------|----------|
|
|
399
|
-
| `avatar` | 128 | Round chips, 28-64 px display @2x |
|
|
400
|
-
| `card` | 500 | Grid / list cards, 200-300 px |
|
|
401
|
-
| `preview` | 960 | Modal / detail view |
|
|
402
|
-
| `hero` | 1600 | Full-bleed hero, edge cases |
|
|
403
|
-
|
|
404
|
-
## Error Handling
|
|
405
|
-
|
|
406
|
-
```ts
|
|
407
|
-
import { Sentroy, SentroyError } from "@sentroy-co/client-sdk"
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
await sentroy.send.email({ ... })
|
|
411
|
-
} catch (err) {
|
|
412
|
-
if (err instanceof SentroyError) {
|
|
413
|
-
console.error(err.statusCode) // 401, 403, 500, etc.
|
|
414
|
-
console.error(err.message) // Human-readable error
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
## Configuration
|
|
420
|
-
|
|
421
|
-
| Option | Type | Required | Description |
|
|
422
|
-
|--------|------|----------|-------------|
|
|
423
|
-
| `baseUrl` | `string` | Yes | Sentroy instance URL (e.g. `https://sentroy.com`) |
|
|
424
|
-
| `companySlug` | `string` | Yes | Your company slug |
|
|
425
|
-
| `accessToken` | `string` | Yes | Access token (`stk_...`) |
|
|
426
|
-
| `timeout` | `number` | No | Request timeout in ms (default: `30000`) |
|
|
427
|
-
|
|
428
|
-
## React components (`@sentroy-co/client-sdk/react`)
|
|
429
|
-
|
|
430
|
-
Optional subpath. Only loaded if you import it; React + react-dom are
|
|
431
|
-
declared as **optional peer dependencies** so server-only consumers
|
|
432
|
-
don't need to install them.
|
|
433
|
-
|
|
434
|
-
```bash
|
|
435
|
-
npm install react react-dom
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### `MediaManager`
|
|
439
|
-
|
|
440
|
-
Drop-in storage browser/uploader for end-user apps. Talks to the same
|
|
441
|
-
Sentroy client you already use; renders Tailwind classes (host app's
|
|
442
|
-
Tailwind setup is reused — the package ships no styles).
|
|
443
|
-
|
|
444
|
-
```tsx
|
|
445
|
-
"use client"
|
|
446
|
-
|
|
447
|
-
import { Sentroy } from "@sentroy-co/client-sdk"
|
|
448
|
-
import { MediaManager } from "@sentroy-co/client-sdk/react"
|
|
449
|
-
|
|
450
|
-
const client = new Sentroy({
|
|
451
|
-
baseUrl: "https://sentroy.com",
|
|
452
|
-
companySlug: "my-company",
|
|
453
|
-
accessToken: "stk_...",
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
export default function Page() {
|
|
457
|
-
return (
|
|
458
|
-
<MediaManager
|
|
459
|
-
client={client}
|
|
460
|
-
multiple
|
|
461
|
-
accept="image/*"
|
|
462
|
-
onChange={(selected) => console.log(selected)}
|
|
463
|
-
onSelect={(selected) => console.log("confirmed:", selected)}
|
|
464
|
-
/>
|
|
465
|
-
)
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
#### Features
|
|
470
|
-
|
|
471
|
-
- Bucket selector (auto-picks first if `bucketSlug` not provided)
|
|
472
|
-
- Search (filename) + file-type filter (image / video / audio / pdf / doc / archive / code)
|
|
473
|
-
- Upload via button **and** drag-and-drop
|
|
474
|
-
- Single or multi selection (`multiple` prop)
|
|
475
|
-
- `initialValue` accepts `Media[]` or `string[]` (id list) — pre-selected
|
|
476
|
-
on mount, fires `onChange` immediately so parent state stays in sync
|
|
477
|
-
- Press `Space` while a card is selected → opens it in fullscreen
|
|
478
|
-
**Lightbox** (image / video / audio render natively, others get a
|
|
479
|
-
download fallback). `Esc` closes, `←/→` step through siblings
|
|
480
|
-
- Detail pane on the right (large screens) — preview, metadata,
|
|
481
|
-
delete, "Use selection" CTA when `onSelect` provided
|
|
482
|
-
|
|
483
|
-
#### Props
|
|
484
|
-
|
|
485
|
-
| Prop | Type | Required | Description |
|
|
486
|
-
|----------------------|-------------------------------------------------------|:-:|:--|
|
|
487
|
-
| `client` | `Sentroy` | Yes | The configured client instance |
|
|
488
|
-
| `bucketSlug` | `string` | | Initial bucket; default = first one in the list |
|
|
489
|
-
| `multiple` | `boolean` | | Allow multi-selection. Default `false` |
|
|
490
|
-
| `maxItems` | `number` | | Cap for multi-mode. New selections are silently blocked once reached. Ignored when `multiple=false` |
|
|
491
|
-
| `accept` | `string` | | File type filter — applies to upload **and** the grid. Same syntax as `<input accept>`: `"image/*"`, `"image/png,image/jpeg"`, `".pdf,.docx"`, comma-separated combos |
|
|
492
|
-
| `initialValue` | `Array<Media \| string>` | | Pre-selected items (objects or ids) |
|
|
493
|
-
| `onChange` | `(selected: Media[]) => void` | | Fires on every selection change |
|
|
494
|
-
| `onSelect` | `(selected: Media[]) => void` | | Fires on confirm — picker dialogs use this |
|
|
495
|
-
| `bucketFilter` | `(b: Bucket) => boolean` | | Filter the bucket dropdown — hide system buckets |
|
|
496
|
-
| `showDetailsPane` | `boolean` | | Default `true` |
|
|
497
|
-
| `showBucketSelector` | `boolean` | | Default `true` |
|
|
498
|
-
| `className` | `string` | | Root wrapper class |
|
|
499
|
-
| `classNames` | `MediaManagerClassNames` | | Per-region class overrides (see theming) |
|
|
500
|
-
|
|
501
|
-
#### Theming
|
|
502
|
-
|
|
503
|
-
The component uses Tailwind utility classes that consume your design
|
|
504
|
-
tokens (`bg-background`, `text-foreground`, `border-border`,
|
|
505
|
-
`text-muted-foreground`, `bg-muted`, etc.). Drop-in usage in any
|
|
506
|
-
shadcn-style codebase needs no extra setup.
|
|
507
|
-
|
|
508
|
-
For finer control, override individual sections via `classNames`:
|
|
509
|
-
|
|
510
|
-
```tsx
|
|
511
|
-
<MediaManager
|
|
512
|
-
client={client}
|
|
513
|
-
className="h-[600px] rounded-2xl border-purple-200"
|
|
514
|
-
classNames={{
|
|
515
|
-
toolbar: "bg-purple-50",
|
|
516
|
-
uploadButton: "bg-purple-600 text-white",
|
|
517
|
-
cardSelected: "ring-purple-400 border-purple-400",
|
|
518
|
-
grid: "sm:grid-cols-2 md:grid-cols-3", // override grid density
|
|
519
|
-
}}
|
|
520
|
-
/>
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
Available keys: `root`, `toolbar`, `searchInput`, `filterSelect`,
|
|
524
|
-
`uploadButton`, `bucketSelect`, `grid`, `card`, `cardSelected`,
|
|
525
|
-
`thumbnail`, `cardMeta`, `empty`, `details`, `dropZoneOverlay`.
|
|
526
|
-
|
|
527
|
-
When you migrate to a different theme system later, change tokens in
|
|
528
|
-
one place — every Tailwind utility resolves through your `globals.css`.
|
|
529
|
-
|
|
530
|
-
### `MediaManagerTrigger`
|
|
531
|
-
|
|
532
|
-
A wrapper that turns **any** clickable element into a media picker — when
|
|
533
|
-
the user clicks the `trigger`, a portal-rendered modal opens with
|
|
534
|
-
`MediaManager` inside, and `onSelect` fires with the confirmed selection.
|
|
535
|
-
|
|
536
|
-
The use case: you don't want a giant manager taking up real estate on your
|
|
537
|
-
profile/settings page — you just want a "Change avatar" button (or even
|
|
538
|
-
a clickable avatar thumbnail) that pops the picker on demand.
|
|
539
|
-
|
|
540
|
-
```tsx
|
|
541
|
-
"use client"
|
|
542
|
-
|
|
543
|
-
import { Sentroy } from "@sentroy-co/client-sdk"
|
|
544
|
-
import { MediaManagerTrigger } from "@sentroy-co/client-sdk/react"
|
|
545
|
-
|
|
546
|
-
const client = new Sentroy({
|
|
547
|
-
baseUrl: "https://sentroy.com",
|
|
548
|
-
companySlug: "my-company",
|
|
549
|
-
accessToken: "stk_...",
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
export function AvatarPicker({
|
|
553
|
-
current,
|
|
554
|
-
onChange,
|
|
555
|
-
}: {
|
|
556
|
-
current: string | null
|
|
557
|
-
onChange: (url: string) => void
|
|
558
|
-
}) {
|
|
559
|
-
return (
|
|
560
|
-
<MediaManagerTrigger
|
|
561
|
-
client={client}
|
|
562
|
-
maxItems={1}
|
|
563
|
-
accept="image/*"
|
|
564
|
-
title="Choose your avatar"
|
|
565
|
-
description="Pick an existing image or upload a new one."
|
|
566
|
-
trigger={
|
|
567
|
-
<button className="rounded-full ring-2 ring-border hover:ring-primary">
|
|
568
|
-
{current ? (
|
|
569
|
-
<img src={current} alt="" className="size-10 rounded-full" />
|
|
570
|
-
) : (
|
|
571
|
-
<span className="grid size-10 place-items-center rounded-full bg-muted text-xs">
|
|
572
|
-
?
|
|
573
|
-
</span>
|
|
574
|
-
)}
|
|
575
|
-
</button>
|
|
576
|
-
}
|
|
577
|
-
onSelect={(media) => {
|
|
578
|
-
if (media[0]?.url) onChange(media[0].url)
|
|
579
|
-
}}
|
|
580
|
-
/>
|
|
581
|
-
)
|
|
582
|
-
}
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
#### Multi-select with cap
|
|
586
|
-
|
|
587
|
-
```tsx
|
|
588
|
-
<MediaManagerTrigger
|
|
589
|
-
client={client}
|
|
590
|
-
maxItems={5}
|
|
591
|
-
accept="image/*,video/*"
|
|
592
|
-
trigger={<Button>Add gallery items</Button>}
|
|
593
|
-
onSelect={(media) => setGallery(media)}
|
|
594
|
-
/>
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
`maxItems > 1` automatically enables multi-mode. Once the user reaches
|
|
598
|
-
the cap, additional clicks on unselected cards are silently no-op'd —
|
|
599
|
-
they have to deselect something to swap.
|
|
600
|
-
|
|
601
|
-
#### Controlled mode
|
|
602
|
-
|
|
603
|
-
If you want the parent to drive open/close (e.g. opening from a context
|
|
604
|
-
menu), pass `open` + `onOpenChange`. The `trigger` is still rendered so
|
|
605
|
-
its click also opens the modal — to render only the modal, pass an empty
|
|
606
|
-
fragment for `trigger`.
|
|
607
|
-
|
|
608
|
-
```tsx
|
|
609
|
-
const [open, setOpen] = useState(false)
|
|
610
|
-
|
|
611
|
-
<MediaManagerTrigger
|
|
612
|
-
client={client}
|
|
613
|
-
open={open}
|
|
614
|
-
onOpenChange={setOpen}
|
|
615
|
-
trigger={<></>}
|
|
616
|
-
onSelect={(media) => { /* … */ }}
|
|
617
|
-
/>
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
#### Props
|
|
621
|
-
|
|
622
|
-
| Prop | Type | Required | Description |
|
|
623
|
-
|--------------------|-----------------------------------|:-:|:--|
|
|
624
|
-
| `client` | `Sentroy` | Yes | Same client you pass to `MediaManager` |
|
|
625
|
-
| `trigger` | `ReactNode` | Yes | The clickable element. Wrapped in `<span role="button">` with click + keyboard (Enter / Space) handlers |
|
|
626
|
-
| `onSelect` | `(selected: Media[]) => void` | Yes | Fires when user confirms; modal auto-closes |
|
|
627
|
-
| `maxItems` | `number` | | `1` = single (default), `>1` = multi up to cap |
|
|
628
|
-
| `accept` | `string` | | Same `<input accept>` syntax — applies to upload **and** grid filter |
|
|
629
|
-
| `title` | `string` | | Modal heading. Default `"Select media"` |
|
|
630
|
-
| `description` | `string` | | Subheading under the title |
|
|
631
|
-
| `open` | `boolean` | | Controlled open state |
|
|
632
|
-
| `onOpenChange` | `(open: boolean) => void` | | Controlled change handler |
|
|
633
|
-
| `disabled` | `boolean` | | Trigger ignores clicks; visual disabled state |
|
|
634
|
-
| `confirmLabel` | `string` | | Default `"Use selection"` |
|
|
635
|
-
| `cancelLabel` | `string` | | Default `"Cancel"` |
|
|
636
|
-
| `modalClassName` | `string` | | Class on the modal panel |
|
|
637
|
-
| `triggerClassName` | `string` | | Class on the trigger wrapper span |
|
|
638
|
-
| … | rest of `MediaManagerProps` | | `bucketSlug`, `bucketFilter`, `showDetailsPane`, `classNames`, etc. forwarded to the inner `MediaManager` |
|
|
639
|
-
|
|
640
|
-
The modal renders into `document.body` via `react-dom` portal, so it
|
|
641
|
-
escapes parent `overflow:hidden` / transform stacking contexts. `Esc`
|
|
642
|
-
closes; backdrop click closes; body scroll is locked while open.
|
|
643
|
-
|
|
644
|
-
#### `Lightbox` (standalone)
|
|
645
|
-
|
|
646
|
-
Exported separately so you can use it outside `MediaManager` (e.g. in
|
|
647
|
-
a feed view):
|
|
648
|
-
|
|
649
|
-
```tsx
|
|
650
|
-
import { Lightbox } from "@sentroy-co/client-sdk/react"
|
|
651
|
-
|
|
652
|
-
const [active, setActive] = useState<Media | null>(null)
|
|
653
|
-
|
|
654
|
-
return (
|
|
655
|
-
<>
|
|
656
|
-
{/* …trigger… */}
|
|
657
|
-
{active && (
|
|
658
|
-
<Lightbox media={active} onClose={() => setActive(null)} />
|
|
659
|
-
)}
|
|
660
|
-
</>
|
|
661
|
-
)
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
Image / video / audio rendered inline; everything else gets a download
|
|
665
|
-
button. `Esc` closes, optional `onPrev` / `onNext` add ←/→ navigation.
|
|
666
|
-
|
|
667
|
-
#### Helpers
|
|
668
|
-
|
|
669
|
-
```ts
|
|
670
|
-
import {
|
|
671
|
-
cn, // tiny class joiner
|
|
672
|
-
formatBytes, // 1234 → "1.21 KB"
|
|
673
|
-
detectKind, // image | video | audio | pdf | doc | archive | code | other
|
|
674
|
-
matchAccept, // matchAccept(file, "image/*,.pdf") → boolean
|
|
675
|
-
KIND_LABELS,
|
|
676
|
-
type MediaKind,
|
|
677
|
-
} from "@sentroy-co/client-sdk/react"
|
|
678
|
-
```
|
|
97
|
+
Sections: [Quickstart](https://docs.sentroy.com) · [Mail](https://docs.sentroy.com/mail) · [Storage](https://docs.sentroy.com/storage) · [React](https://docs.sentroy.com/react) · [Tools](https://docs.sentroy.com/tools)
|
|
679
98
|
|
|
680
99
|
## Requirements
|
|
681
100
|
|
|
@@ -683,12 +102,12 @@ import {
|
|
|
683
102
|
- React 18+ (only if you import from `/react`)
|
|
684
103
|
- Tailwind CSS in the host app (only for React components)
|
|
685
104
|
|
|
686
|
-
##
|
|
105
|
+
## For AI agents
|
|
687
106
|
|
|
688
|
-
|
|
107
|
+
A single-file, comprehensive reference covering every endpoint, parameter and response shape lives at [`AGENTS.md`](AGENTS.md). Drop the raw URL into a context window:
|
|
689
108
|
|
|
690
109
|
```
|
|
691
|
-
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/typescript/
|
|
110
|
+
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/typescript/AGENTS.md
|
|
692
111
|
```
|
|
693
112
|
|
|
694
113
|
## License
|