@sentroy-co/client-sdk 2.5.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +10 -0
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/react/lib/use-upload-queue.d.ts.map +1 -1
- package/dist/react/lib/use-upload-queue.js +61 -25
- package/dist/react/lib/use-upload-queue.js.map +1 -1
- package/dist/resources/audience.d.ts +60 -0
- package/dist/resources/audience.d.ts.map +1 -0
- package/dist/resources/audience.js +108 -0
- package/dist/resources/audience.js.map +1 -0
- package/dist/resources/logs.d.ts +15 -0
- package/dist/resources/logs.d.ts.map +1 -0
- package/dist/resources/logs.js +36 -0
- package/dist/resources/logs.js.map +1 -0
- package/dist/resources/suppressions.d.ts +20 -0
- package/dist/resources/suppressions.d.ts.map +1 -0
- package/dist/resources/suppressions.js +39 -0
- package/dist/resources/suppressions.js.map +1 -0
- package/dist/resources/webhooks.d.ts +21 -0
- package/dist/resources/webhooks.d.ts.map +1 -0
- package/dist/resources/webhooks.js +35 -0
- package/dist/resources/webhooks.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/http.ts +11 -0
- package/src/index.ts +30 -0
- package/src/react/lib/use-upload-queue.ts +64 -25
- package/src/resources/audience.ts +139 -0
- package/src/resources/logs.ts +27 -0
- package/src/resources/suppressions.ts +39 -0
- package/src/resources/webhooks.ts +47 -0
- package/src/types.ts +161 -0
|
@@ -66,6 +66,22 @@ export function useUploadQueue(
|
|
|
66
66
|
// Worker pump — queued entry varsa ve aktif < concurrency ise başlat.
|
|
67
67
|
const pumpRef = useRef<() => void>(() => {})
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Aktif (in-flight) upload sayısını synchronous olarak takip eden ref.
|
|
71
|
+
* `entries` state setEntries ile async güncellendiğinden, recursive
|
|
72
|
+
* `pump` çağrılarında `entries.filter(e => e.status === "uploading")`
|
|
73
|
+
* eski listeyi görür → aynı queued entry birden çok kez başlatılır
|
|
74
|
+
* (Chrome side `ERR_INSUFFICIENT_RESOURCES`). Ref ile incre/decre
|
|
75
|
+
* synchronous; concurrency limiti gerçekten devreye girer.
|
|
76
|
+
*/
|
|
77
|
+
const inFlightRef = useRef(0)
|
|
78
|
+
/**
|
|
79
|
+
* Henüz başlatılmamış queued entry id'lerinin sıralı listesi. setEntries
|
|
80
|
+
* async olduğu için listeden seçim yapmak yarış koşulu üretir; ref
|
|
81
|
+
* üzerinden FIFO push/shift hem deterministik hem hızlı.
|
|
82
|
+
*/
|
|
83
|
+
const queueRef = useRef<string[]>([])
|
|
84
|
+
|
|
69
85
|
const updateEntry = useCallback(
|
|
70
86
|
(id: string, patch: Partial<UploadEntry>) => {
|
|
71
87
|
setEntries((prev) =>
|
|
@@ -76,65 +92,77 @@ export function useUploadQueue(
|
|
|
76
92
|
)
|
|
77
93
|
|
|
78
94
|
pumpRef.current = () => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
// Slot doluysa veya queue boşsa erken dön.
|
|
96
|
+
if (inFlightRef.current >= concurrency) return
|
|
97
|
+
const nextId = queueRef.current.shift()
|
|
98
|
+
if (!nextId) return
|
|
99
|
+
|
|
100
|
+
// Slot'u synchronously rezerve et — bir sonraki pump çağrısı bu
|
|
101
|
+
// entry'i tekrar shift edemez.
|
|
102
|
+
inFlightRef.current++
|
|
103
|
+
|
|
104
|
+
const entry = entriesRef.current.find((e) => e.id === nextId)
|
|
105
|
+
if (!entry) {
|
|
106
|
+
// Cancel öncesi remove edilmiş; slot'u iade et ve pump'a devam.
|
|
107
|
+
inFlightRef.current--
|
|
108
|
+
pumpRef.current?.()
|
|
109
|
+
return
|
|
110
|
+
}
|
|
84
111
|
|
|
85
112
|
// Mark uploading
|
|
86
|
-
updateEntry(
|
|
113
|
+
updateEntry(entry.id, { status: "uploading" })
|
|
87
114
|
|
|
88
|
-
|
|
89
|
-
// gerekmez); enqueue closure'unda capture edilir, ayrı bucket map.
|
|
90
|
-
const bucketSlug = bucketMapRef.current[next.id]
|
|
115
|
+
const bucketSlug = bucketMapRef.current[entry.id]
|
|
91
116
|
if (!bucketSlug) {
|
|
92
|
-
updateEntry(
|
|
117
|
+
updateEntry(entry.id, { status: "error", error: "No bucket" })
|
|
118
|
+
inFlightRef.current--
|
|
93
119
|
pumpRef.current?.()
|
|
94
120
|
return
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
const controller = new AbortController()
|
|
98
|
-
cancelMapRef.current[
|
|
124
|
+
cancelMapRef.current[entry.id] = () => controller.abort()
|
|
99
125
|
|
|
100
126
|
client.media
|
|
101
127
|
.upload(
|
|
102
128
|
bucketSlug,
|
|
103
|
-
{ body:
|
|
129
|
+
{ body: entry.file, filename: entry.file.name },
|
|
104
130
|
{
|
|
105
131
|
onProgress: (loaded, total) => {
|
|
106
|
-
updateEntry(
|
|
132
|
+
updateEntry(entry.id, { loaded, total })
|
|
107
133
|
},
|
|
108
134
|
signal: controller.signal,
|
|
109
135
|
},
|
|
110
136
|
)
|
|
111
137
|
.then((media) => {
|
|
112
|
-
updateEntry(
|
|
138
|
+
updateEntry(entry.id, {
|
|
113
139
|
status: "done",
|
|
114
140
|
media,
|
|
115
|
-
loaded:
|
|
116
|
-
total:
|
|
141
|
+
loaded: entry.file.size,
|
|
142
|
+
total: entry.file.size,
|
|
117
143
|
})
|
|
118
144
|
onUploadedRef.current?.(media)
|
|
119
145
|
})
|
|
120
146
|
.catch((err: unknown) => {
|
|
121
147
|
const aborted =
|
|
122
148
|
(err as { message?: string })?.message === "Upload aborted"
|
|
123
|
-
updateEntry(
|
|
149
|
+
updateEntry(entry.id, {
|
|
124
150
|
status: aborted ? "canceled" : "error",
|
|
125
151
|
error: aborted
|
|
126
152
|
? undefined
|
|
127
|
-
: (err as Error)?.message ?? "Upload failed",
|
|
153
|
+
: ((err as Error)?.message ?? "Upload failed"),
|
|
128
154
|
})
|
|
129
155
|
})
|
|
130
156
|
.finally(() => {
|
|
131
|
-
delete cancelMapRef.current[
|
|
132
|
-
|
|
157
|
+
delete cancelMapRef.current[entry.id]
|
|
158
|
+
inFlightRef.current--
|
|
159
|
+
// Slot açıldı, sıradakini başlat.
|
|
133
160
|
pumpRef.current?.()
|
|
134
161
|
})
|
|
135
162
|
|
|
136
|
-
// Aynı tick'te
|
|
137
|
-
|
|
163
|
+
// Aynı tick'te kalan slot'ları doldur — concurrency artık
|
|
164
|
+
// synchronously inFlightRef ile guard'lı, çift başlatma yok.
|
|
165
|
+
if (inFlightRef.current < concurrency) pumpRef.current?.()
|
|
138
166
|
}
|
|
139
167
|
|
|
140
168
|
const bucketMapRef = useRef<Record<string, string>>({})
|
|
@@ -146,6 +174,9 @@ export function useUploadQueue(
|
|
|
146
174
|
const newEntries: UploadEntry[] = files.map((file) => {
|
|
147
175
|
const id = `up-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
148
176
|
bucketMapRef.current[id] = bucketSlug
|
|
177
|
+
// FIFO queue — pump bu sıradan shift eder; setEntries async
|
|
178
|
+
// güncellemesinden bağımsız synchronous source-of-truth.
|
|
179
|
+
queueRef.current.push(id)
|
|
149
180
|
return {
|
|
150
181
|
id,
|
|
151
182
|
file,
|
|
@@ -155,7 +186,8 @@ export function useUploadQueue(
|
|
|
155
186
|
cancel: () => {
|
|
156
187
|
const c = cancelMapRef.current[id]
|
|
157
188
|
if (c) c()
|
|
158
|
-
else
|
|
189
|
+
else {
|
|
190
|
+
queueRef.current = queueRef.current.filter((qid) => qid !== id)
|
|
159
191
|
setEntries((prev) =>
|
|
160
192
|
prev.map((e) =>
|
|
161
193
|
e.id === id && e.status === "queued"
|
|
@@ -163,11 +195,15 @@ export function useUploadQueue(
|
|
|
163
195
|
: e,
|
|
164
196
|
),
|
|
165
197
|
)
|
|
198
|
+
}
|
|
166
199
|
},
|
|
167
200
|
}
|
|
168
201
|
})
|
|
169
202
|
setEntries((prev) => [...prev, ...newEntries])
|
|
170
|
-
// pump on next tick — state update'ten sonra entriesRef güncel olsun
|
|
203
|
+
// pump on next tick — state update'ten sonra entriesRef güncel olsun.
|
|
204
|
+
// pumpRef kendi içinde sequential pump zincirini sürdürür (her
|
|
205
|
+
// başarılı slot rezervasyonundan sonra bir dahaki pump'ı çağırır),
|
|
206
|
+
// dolayısıyla burada tek tetikleme yeterli.
|
|
171
207
|
Promise.resolve().then(() => pumpRef.current?.())
|
|
172
208
|
},
|
|
173
209
|
[],
|
|
@@ -176,7 +212,8 @@ export function useUploadQueue(
|
|
|
176
212
|
const cancel = useCallback((id: string) => {
|
|
177
213
|
const c = cancelMapRef.current[id]
|
|
178
214
|
if (c) c()
|
|
179
|
-
else
|
|
215
|
+
else {
|
|
216
|
+
queueRef.current = queueRef.current.filter((qid) => qid !== id)
|
|
180
217
|
setEntries((prev) =>
|
|
181
218
|
prev.map((e) =>
|
|
182
219
|
e.id === id && e.status === "queued"
|
|
@@ -184,10 +221,12 @@ export function useUploadQueue(
|
|
|
184
221
|
: e,
|
|
185
222
|
),
|
|
186
223
|
)
|
|
224
|
+
}
|
|
187
225
|
}, [])
|
|
188
226
|
|
|
189
227
|
const remove = useCallback((id: string) => {
|
|
190
228
|
setEntries((prev) => prev.filter((e) => e.id !== id))
|
|
229
|
+
queueRef.current = queueRef.current.filter((qid) => qid !== id)
|
|
191
230
|
delete bucketMapRef.current[id]
|
|
192
231
|
delete cancelMapRef.current[id]
|
|
193
232
|
}, [])
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { HttpClient } from "../http"
|
|
2
|
+
import type {
|
|
3
|
+
Contact,
|
|
4
|
+
ContactList,
|
|
5
|
+
ContactListParams,
|
|
6
|
+
ContactListResult,
|
|
7
|
+
CreateAudienceListParams,
|
|
8
|
+
CreateContactParams,
|
|
9
|
+
UpdateContactParams,
|
|
10
|
+
} from "../types"
|
|
11
|
+
|
|
12
|
+
class AudienceListMembers {
|
|
13
|
+
constructor(
|
|
14
|
+
private http: HttpClient,
|
|
15
|
+
private listId: string,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
/** List all contacts in this audience list. */
|
|
19
|
+
async list(): Promise<Contact[]> {
|
|
20
|
+
return this.http.get<Contact[]>(
|
|
21
|
+
`/audience/lists/${encodeURIComponent(this.listId)}/members`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Add a contact to the list by id. */
|
|
26
|
+
async add(contactId: string): Promise<void> {
|
|
27
|
+
await this.http.post<{ message: string }>(
|
|
28
|
+
`/audience/lists/${encodeURIComponent(this.listId)}/members`,
|
|
29
|
+
{ contactId },
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Remove a contact from the list. The contact record itself is preserved. */
|
|
34
|
+
async remove(contactId: string): Promise<void> {
|
|
35
|
+
await this.http.delWithBody<{ message: string }>(
|
|
36
|
+
`/audience/lists/${encodeURIComponent(this.listId)}/members`,
|
|
37
|
+
{ contactId },
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class AudienceLists {
|
|
43
|
+
constructor(private http: HttpClient) {}
|
|
44
|
+
|
|
45
|
+
/** List every audience list in the company. */
|
|
46
|
+
async list(): Promise<ContactList[]> {
|
|
47
|
+
return this.http.get<ContactList[]>("/audience/lists")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Get a single audience list by id. */
|
|
51
|
+
async get(id: string): Promise<ContactList> {
|
|
52
|
+
return this.http.get<ContactList>(
|
|
53
|
+
`/audience/lists/${encodeURIComponent(id)}`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Create a new audience list. */
|
|
58
|
+
async create(params: CreateAudienceListParams): Promise<ContactList> {
|
|
59
|
+
return this.http.post<ContactList>("/audience/lists", params)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Delete an audience list. Contacts stay in the company; only the grouping is removed. */
|
|
63
|
+
async delete(id: string): Promise<void> {
|
|
64
|
+
await this.http.del<{ message: string }>(
|
|
65
|
+
`/audience/lists/${encodeURIComponent(id)}`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Membership operations scoped to a list id. */
|
|
70
|
+
members(listId: string): AudienceListMembers {
|
|
71
|
+
return new AudienceListMembers(this.http, listId)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class AudienceContacts {
|
|
76
|
+
constructor(private http: HttpClient) {}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Paginated list of contacts. Filter by status or tag set; tags are
|
|
80
|
+
* sent as a comma-joined query param.
|
|
81
|
+
*/
|
|
82
|
+
async list(params?: ContactListParams): Promise<ContactListResult> {
|
|
83
|
+
const query: Record<string, unknown> = {}
|
|
84
|
+
if (params?.page !== undefined) query.page = params.page
|
|
85
|
+
if (params?.limit !== undefined) query.limit = params.limit
|
|
86
|
+
if (params?.status) query.status = params.status
|
|
87
|
+
if (params?.tags?.length) query.tags = params.tags.join(",")
|
|
88
|
+
return this.http.get<ContactListResult>("/audience/contacts", query)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Email-prefix autocomplete. Capped server-side at 10 results — use
|
|
93
|
+
* `list` for paginated browsing.
|
|
94
|
+
*/
|
|
95
|
+
async search(q: string): Promise<Contact[]> {
|
|
96
|
+
return this.http.get<Contact[]>("/audience/contacts", { q })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Get a single contact by id. */
|
|
100
|
+
async get(id: string): Promise<Contact> {
|
|
101
|
+
return this.http.get<Contact>(
|
|
102
|
+
`/audience/contacts/${encodeURIComponent(id)}`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Create a contact. Defaults to status: "active". */
|
|
107
|
+
async create(params: CreateContactParams): Promise<Contact> {
|
|
108
|
+
return this.http.post<Contact>("/audience/contacts", params)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Patch any contact field. Pass `status` to mark unsubscribed/bounced. */
|
|
112
|
+
async update(id: string, params: UpdateContactParams): Promise<Contact> {
|
|
113
|
+
return this.http.patch<Contact>(
|
|
114
|
+
`/audience/contacts/${encodeURIComponent(id)}`,
|
|
115
|
+
params,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Soft-delete: marks the contact as `unsubscribed`. The record stays so
|
|
121
|
+
* historical mail-log foreign keys keep resolving and the email won't
|
|
122
|
+
* accidentally be re-added.
|
|
123
|
+
*/
|
|
124
|
+
async delete(id: string): Promise<void> {
|
|
125
|
+
await this.http.del<{ message: string }>(
|
|
126
|
+
`/audience/contacts/${encodeURIComponent(id)}`,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class Audience {
|
|
132
|
+
public readonly lists: AudienceLists
|
|
133
|
+
public readonly contacts: AudienceContacts
|
|
134
|
+
|
|
135
|
+
constructor(http: HttpClient) {
|
|
136
|
+
this.lists = new AudienceLists(http)
|
|
137
|
+
this.contacts = new AudienceContacts(http)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HttpClient } from "../http"
|
|
2
|
+
import type { LogListParams, MailLog } from "../types"
|
|
3
|
+
|
|
4
|
+
export class Logs {
|
|
5
|
+
constructor(private http: HttpClient) {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* List mail-log entries. Filter by status, domain, and ISO timestamp
|
|
9
|
+
* range (`from` / `to`). Results are paginated server-side; pass `page`
|
|
10
|
+
* and `limit` to walk a large window.
|
|
11
|
+
*/
|
|
12
|
+
async list(params?: LogListParams): Promise<MailLog[]> {
|
|
13
|
+
const query: Record<string, unknown> = {}
|
|
14
|
+
if (params?.page !== undefined) query.page = params.page
|
|
15
|
+
if (params?.limit !== undefined) query.limit = params.limit
|
|
16
|
+
if (params?.status) query.status = params.status
|
|
17
|
+
if (params?.domainId) query.domainId = params.domainId
|
|
18
|
+
if (params?.from) query.from = params.from
|
|
19
|
+
if (params?.to) query.to = params.to
|
|
20
|
+
return this.http.get<MailLog[]>("/logs", query)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Get a single mail-log entry by id. */
|
|
24
|
+
async get(id: string): Promise<MailLog> {
|
|
25
|
+
return this.http.get<MailLog>(`/logs/${encodeURIComponent(id)}`)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HttpClient } from "../http"
|
|
2
|
+
import type {
|
|
3
|
+
AddSuppressionParams,
|
|
4
|
+
Suppression,
|
|
5
|
+
SuppressionListParams,
|
|
6
|
+
} from "../types"
|
|
7
|
+
|
|
8
|
+
export class Suppressions {
|
|
9
|
+
constructor(private http: HttpClient) {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* List suppressions across the company (or a single domain). Suppressed
|
|
13
|
+
* recipients are skipped at send time — every entry here is one address
|
|
14
|
+
* that will not receive mail until removed.
|
|
15
|
+
*/
|
|
16
|
+
async list(params?: SuppressionListParams): Promise<Suppression[]> {
|
|
17
|
+
const query: Record<string, unknown> = {}
|
|
18
|
+
if (params?.page !== undefined) query.page = params.page
|
|
19
|
+
if (params?.limit !== undefined) query.limit = params.limit
|
|
20
|
+
if (params?.domainId) query.domainId = params.domainId
|
|
21
|
+
if (params?.reason) query.reason = params.reason
|
|
22
|
+
return this.http.get<Suppression[]>("/suppressions", query)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manually suppress an address (e.g. honoring an off-platform opt-out).
|
|
27
|
+
* Bounces and complaints are added automatically by the mail server.
|
|
28
|
+
*/
|
|
29
|
+
async add(params: AddSuppressionParams): Promise<Suppression> {
|
|
30
|
+
return this.http.post<Suppression>("/suppressions", params)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Remove a suppression — the address will be eligible to receive mail again. */
|
|
34
|
+
async remove(id: string): Promise<void> {
|
|
35
|
+
await this.http.del<{ message: string }>(
|
|
36
|
+
`/suppressions/${encodeURIComponent(id)}`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HttpClient } from "../http"
|
|
2
|
+
import type {
|
|
3
|
+
CreateWebhookParams,
|
|
4
|
+
UpdateWebhookParams,
|
|
5
|
+
Webhook,
|
|
6
|
+
} from "../types"
|
|
7
|
+
|
|
8
|
+
export class Webhooks {
|
|
9
|
+
constructor(private http: HttpClient) {}
|
|
10
|
+
|
|
11
|
+
/** List webhooks across the company, or scoped to a single domain. */
|
|
12
|
+
async list(domainId?: string): Promise<Webhook[]> {
|
|
13
|
+
return this.http.get<Webhook[]>(
|
|
14
|
+
"/webhooks",
|
|
15
|
+
domainId ? { domainId } : undefined,
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Get a single webhook by id. */
|
|
20
|
+
async get(id: string): Promise<Webhook> {
|
|
21
|
+
return this.http.get<Webhook>(`/webhooks/${encodeURIComponent(id)}`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Register a webhook for one or more events on a domain. The response
|
|
26
|
+
* includes a `secret` — store it now; subsequent reads only return the
|
|
27
|
+
* webhook config without the secret.
|
|
28
|
+
*/
|
|
29
|
+
async create(params: CreateWebhookParams): Promise<Webhook> {
|
|
30
|
+
return this.http.post<Webhook>("/webhooks", params)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Patch URL, event list, or `active` flag. */
|
|
34
|
+
async update(id: string, params: UpdateWebhookParams): Promise<Webhook> {
|
|
35
|
+
return this.http.patch<Webhook>(
|
|
36
|
+
`/webhooks/${encodeURIComponent(id)}`,
|
|
37
|
+
params,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Delete a webhook. In-flight deliveries are not retried. */
|
|
42
|
+
async delete(id: string): Promise<void> {
|
|
43
|
+
await this.http.del<{ message: string }>(
|
|
44
|
+
`/webhooks/${encodeURIComponent(id)}`,
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -322,3 +322,164 @@ export interface StorageUsage {
|
|
|
322
322
|
buckets: StorageUsageBucket[]
|
|
323
323
|
byType: StorageUsageByType[]
|
|
324
324
|
}
|
|
325
|
+
|
|
326
|
+
// ── Audience / Contacts ───────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
export type ContactStatus = "active" | "unsubscribed" | "bounced"
|
|
329
|
+
|
|
330
|
+
export interface Contact {
|
|
331
|
+
id: string
|
|
332
|
+
companyId: string
|
|
333
|
+
email: string
|
|
334
|
+
name?: string
|
|
335
|
+
tags: string[]
|
|
336
|
+
status: ContactStatus
|
|
337
|
+
metadata: Record<string, unknown>
|
|
338
|
+
lastEmailedAt?: string | null
|
|
339
|
+
createdAt: string
|
|
340
|
+
updatedAt: string
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface ContactList {
|
|
344
|
+
id: string
|
|
345
|
+
companyId: string
|
|
346
|
+
name: string
|
|
347
|
+
description?: string
|
|
348
|
+
memberCount?: number
|
|
349
|
+
createdAt: string
|
|
350
|
+
updatedAt: string
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface CreateContactParams {
|
|
354
|
+
email: string
|
|
355
|
+
name?: string
|
|
356
|
+
tags?: string[]
|
|
357
|
+
metadata?: Record<string, unknown>
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface UpdateContactParams {
|
|
361
|
+
email?: string
|
|
362
|
+
name?: string
|
|
363
|
+
tags?: string[]
|
|
364
|
+
status?: ContactStatus
|
|
365
|
+
metadata?: Record<string, unknown>
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface ContactListParams {
|
|
369
|
+
page?: number
|
|
370
|
+
limit?: number
|
|
371
|
+
status?: ContactStatus
|
|
372
|
+
/** Comma-joined when sent over the wire — pass an array, the SDK joins. */
|
|
373
|
+
tags?: string[]
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface ContactListResult {
|
|
377
|
+
contacts: Contact[]
|
|
378
|
+
total: number
|
|
379
|
+
page: number
|
|
380
|
+
limit: number
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export interface CreateAudienceListParams {
|
|
384
|
+
name: string
|
|
385
|
+
description?: string
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── Suppressions ──────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
export interface Suppression {
|
|
391
|
+
id: string
|
|
392
|
+
email: string
|
|
393
|
+
reason: string
|
|
394
|
+
domainId: string
|
|
395
|
+
createdAt: string
|
|
396
|
+
domain?: { domain: string }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export interface AddSuppressionParams {
|
|
400
|
+
email: string
|
|
401
|
+
/** Free-form label (e.g. "manual", "complaint"). Defaults backend-side. */
|
|
402
|
+
reason?: string
|
|
403
|
+
domainId: string
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export interface SuppressionListParams {
|
|
407
|
+
page?: number
|
|
408
|
+
limit?: number
|
|
409
|
+
domainId?: string
|
|
410
|
+
reason?: string
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── Webhooks ──────────────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
export type WebhookEvent =
|
|
416
|
+
| "sent"
|
|
417
|
+
| "bounced"
|
|
418
|
+
| "failed"
|
|
419
|
+
| "opened"
|
|
420
|
+
| "clicked"
|
|
421
|
+
| "unsubscribed"
|
|
422
|
+
|
|
423
|
+
export interface Webhook {
|
|
424
|
+
id: string
|
|
425
|
+
url: string
|
|
426
|
+
events: string[]
|
|
427
|
+
active: boolean
|
|
428
|
+
domainId: string
|
|
429
|
+
/** Returned only on create — used to verify HMAC signatures of deliveries. */
|
|
430
|
+
secret?: string
|
|
431
|
+
createdAt: string
|
|
432
|
+
updatedAt: string
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export interface CreateWebhookParams {
|
|
436
|
+
url: string
|
|
437
|
+
events: WebhookEvent[] | string[]
|
|
438
|
+
domainId: string
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface UpdateWebhookParams {
|
|
442
|
+
url?: string
|
|
443
|
+
events?: WebhookEvent[] | string[]
|
|
444
|
+
active?: boolean
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ── Logs ──────────────────────────────────────────────────────────────────
|
|
448
|
+
|
|
449
|
+
export type MailLogStatus =
|
|
450
|
+
| "queued"
|
|
451
|
+
| "processing"
|
|
452
|
+
| "sent"
|
|
453
|
+
| "bounced"
|
|
454
|
+
| "failed"
|
|
455
|
+
|
|
456
|
+
export interface MailLog {
|
|
457
|
+
id: string
|
|
458
|
+
to: string
|
|
459
|
+
from: string
|
|
460
|
+
subject: string
|
|
461
|
+
status: MailLogStatus
|
|
462
|
+
messageId: string | null
|
|
463
|
+
domainId: string
|
|
464
|
+
domain?: { domain: string }
|
|
465
|
+
templateId: string | null
|
|
466
|
+
variables: Record<string, unknown> | null
|
|
467
|
+
scheduledAt?: string | null
|
|
468
|
+
sentAt: string | null
|
|
469
|
+
bouncedAt: string | null
|
|
470
|
+
openedAt?: string | null
|
|
471
|
+
clickedAt?: string | null
|
|
472
|
+
error: string | null
|
|
473
|
+
createdAt: string
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface LogListParams {
|
|
477
|
+
page?: number
|
|
478
|
+
limit?: number
|
|
479
|
+
status?: MailLogStatus
|
|
480
|
+
domainId?: string
|
|
481
|
+
/** ISO timestamp lower bound (inclusive). */
|
|
482
|
+
from?: string
|
|
483
|
+
/** ISO timestamp upper bound (inclusive). */
|
|
484
|
+
to?: string
|
|
485
|
+
}
|