retainful-shopify-sdk 1.0.2
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 +443 -0
- package/dist/api/events.d.ts +45 -0
- package/dist/api/events.d.ts.map +1 -0
- package/dist/api/events.js +86 -0
- package/dist/api/events.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +8 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +43 -0
- package/dist/session.js.map +1 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +27 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/retry.d.ts +7 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +65 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# @yuko-app/retainful-shopify-sdk
|
|
2
|
+
|
|
3
|
+
A typed TypeScript SDK for sending Shopify app lifecycle events to [Retainful](https://retainful.com).
|
|
4
|
+
Built to be shared across all Yuko Apps — configure once per app, use everywhere.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @yuko-app/retainful-shopify-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or, if using a monorepo with a local path:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
// package.json of your Shopify app
|
|
18
|
+
{
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@yuko-app/retainful-shopify-sdk": "file:../../packages/retainful-sdk"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
### 1. Get your credentials from Retainful
|
|
30
|
+
|
|
31
|
+
| Credential | Where to find it |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `apiKey` | Retainful Dashboard → Settings → API Keys |
|
|
34
|
+
|
|
35
|
+
### 2. Instantiate the client
|
|
36
|
+
|
|
37
|
+
Create **one client per Shopify app** and export it for reuse.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// lib/retainful.ts
|
|
41
|
+
import { RetainfulClient } from "@yuko-app/retainful-shopify-sdk";
|
|
42
|
+
|
|
43
|
+
export const retainful = new RetainfulClient(
|
|
44
|
+
{
|
|
45
|
+
apiKey: process.env.RETAINFUL_API_KEY!,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
// Set these once — they're stamped on every event automatically
|
|
49
|
+
appName: "Order Tracking Pro",
|
|
50
|
+
appSlug: "order-tracking-pro",
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **One client per app.** The `appName` and `appSlug` are baked into the client,
|
|
56
|
+
> so you never repeat them at the call site.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Track an Install
|
|
63
|
+
|
|
64
|
+
Call this inside your Shopify auth callback or `app/installed` webhook handler.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { retainful } from "~/lib/retainful";
|
|
68
|
+
|
|
69
|
+
// Inside your install handler
|
|
70
|
+
await retainful.events.installed({
|
|
71
|
+
email: shop.email,
|
|
72
|
+
shopDomain: shop.myshopify_domain, // e.g. "my-store.myshopify.com"
|
|
73
|
+
shopDetails: {
|
|
74
|
+
name: shop.name,
|
|
75
|
+
ownerName: shop.shop_owner,
|
|
76
|
+
plan: shop.plan_name,
|
|
77
|
+
country: shop.country_name,
|
|
78
|
+
currency: shop.currency,
|
|
79
|
+
timezone: shop.timezone,
|
|
80
|
+
phone: shop.phone,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Track an Uninstall
|
|
86
|
+
|
|
87
|
+
Call this inside your `app/uninstalled` webhook handler.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { retainful } from "~/lib/retainful";
|
|
91
|
+
|
|
92
|
+
// Inside your uninstall webhook handler
|
|
93
|
+
await retainful.events.uninstalled({
|
|
94
|
+
email: shop.email,
|
|
95
|
+
shopDomain: shop.myshopify_domain,
|
|
96
|
+
shopDetails: {
|
|
97
|
+
name: shop.name,
|
|
98
|
+
ownerName: shop.shop_owner,
|
|
99
|
+
plan: shop.plan_name,
|
|
100
|
+
country: shop.country_name,
|
|
101
|
+
currency: shop.currency,
|
|
102
|
+
timezone: shop.timezone,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Framework Setup
|
|
110
|
+
|
|
111
|
+
The SDK is framework-agnostic — it only needs a place to instantiate the client once
|
|
112
|
+
and a place to call `events.installed()` / `events.uninstalled()` (usually a webhook
|
|
113
|
+
or auth callback). Below are idiomatic wirings for the common stacks.
|
|
114
|
+
|
|
115
|
+
> In every example, set `RETAINFUL_API_KEY` in your environment and **never throw**
|
|
116
|
+
> on a Retainful failure — a tracking error should not break your webhook response.
|
|
117
|
+
|
|
118
|
+
### Remix
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// app/lib/retainful.ts
|
|
122
|
+
import { RetainfulClient } from "@yuko-app/retainful-shopify-sdk";
|
|
123
|
+
|
|
124
|
+
export const retainful = new RetainfulClient(
|
|
125
|
+
{ apiKey: process.env.RETAINFUL_API_KEY! },
|
|
126
|
+
{ appName: "Order Tracking Pro", appSlug: "order-tracking-pro" }
|
|
127
|
+
);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// app/routes/webhooks.tsx
|
|
132
|
+
import type { ActionFunctionArgs } from "@remix-run/node";
|
|
133
|
+
import { retainful } from "~/lib/retainful";
|
|
134
|
+
import { RetainfulApiError } from "@yuko-app/retainful-shopify-sdk";
|
|
135
|
+
|
|
136
|
+
export const action = async ({ request }: ActionFunctionArgs) => {
|
|
137
|
+
const topic = request.headers.get("x-shopify-topic");
|
|
138
|
+
const shop = await request.json();
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
if (topic === "app/uninstalled") {
|
|
142
|
+
await retainful.events.uninstalled({
|
|
143
|
+
email: shop.email,
|
|
144
|
+
shopDomain: shop.myshopify_domain,
|
|
145
|
+
shopDetails: {
|
|
146
|
+
name: shop.name,
|
|
147
|
+
ownerName: shop.shop_owner,
|
|
148
|
+
plan: shop.plan_name,
|
|
149
|
+
country: shop.country_name,
|
|
150
|
+
currency: shop.currency,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof RetainfulApiError) {
|
|
156
|
+
console.error(`Retainful error ${error.statusCode}:`, error.statusText);
|
|
157
|
+
// Don't throw — don't let Retainful failures break your webhook response
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return new Response("OK", { status: 200 });
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Next.js (App Router)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// lib/retainful.ts
|
|
169
|
+
import { RetainfulClient } from "@yuko-app/retainful-shopify-sdk";
|
|
170
|
+
|
|
171
|
+
export const retainful = new RetainfulClient(
|
|
172
|
+
{ apiKey: process.env.RETAINFUL_API_KEY! },
|
|
173
|
+
{ appName: "Order Tracking Pro", appSlug: "order-tracking-pro" }
|
|
174
|
+
);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// app/api/webhooks/route.ts
|
|
179
|
+
import { NextResponse } from "next/server";
|
|
180
|
+
import { retainful } from "@/lib/retainful";
|
|
181
|
+
import { RetainfulApiError } from "@yuko-app/retainful-shopify-sdk";
|
|
182
|
+
|
|
183
|
+
// Webhooks must run on the Node.js runtime, not the Edge runtime.
|
|
184
|
+
export const runtime = "nodejs";
|
|
185
|
+
|
|
186
|
+
export async function POST(request: Request) {
|
|
187
|
+
const topic = request.headers.get("x-shopify-topic");
|
|
188
|
+
const shop = await request.json();
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
if (topic === "app/uninstalled") {
|
|
192
|
+
await retainful.events.uninstalled({
|
|
193
|
+
email: shop.email,
|
|
194
|
+
shopDomain: shop.myshopify_domain,
|
|
195
|
+
shopDetails: {
|
|
196
|
+
name: shop.name,
|
|
197
|
+
ownerName: shop.shop_owner,
|
|
198
|
+
plan: shop.plan_name,
|
|
199
|
+
country: shop.country_name,
|
|
200
|
+
currency: shop.currency,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error instanceof RetainfulApiError) {
|
|
206
|
+
console.error(`Retainful error ${error.statusCode}:`, error.statusText);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return NextResponse.json({ ok: true });
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
> **Pages Router?** The same `retainful` client works inside
|
|
215
|
+
> `pages/api/webhooks.ts` — read `req.headers["x-shopify-topic"]` and `req.body`,
|
|
216
|
+
> then `res.status(200).json({ ok: true })`.
|
|
217
|
+
|
|
218
|
+
### NestJS
|
|
219
|
+
|
|
220
|
+
Wrap the client in an injectable provider so it can be shared across the app via DI.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// retainful/retainful.module.ts
|
|
224
|
+
import { Module } from "@nestjs/common";
|
|
225
|
+
import { RetainfulClient } from "@yuko-app/retainful-shopify-sdk";
|
|
226
|
+
|
|
227
|
+
export const RETAINFUL = "RETAINFUL_CLIENT";
|
|
228
|
+
|
|
229
|
+
@Module({
|
|
230
|
+
providers: [
|
|
231
|
+
{
|
|
232
|
+
provide: RETAINFUL,
|
|
233
|
+
useFactory: () =>
|
|
234
|
+
new RetainfulClient(
|
|
235
|
+
{ apiKey: process.env.RETAINFUL_API_KEY! },
|
|
236
|
+
{ appName: "Order Tracking Pro", appSlug: "order-tracking-pro" }
|
|
237
|
+
),
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
exports: [RETAINFUL],
|
|
241
|
+
})
|
|
242
|
+
export class RetainfulModule {}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// webhooks/webhooks.controller.ts
|
|
247
|
+
import { Controller, Post, Body, Headers, Inject } from "@nestjs/common";
|
|
248
|
+
import { RetainfulClient, RetainfulApiError } from "@yuko-app/retainful-shopify-sdk";
|
|
249
|
+
import { RETAINFUL } from "../retainful/retainful.module";
|
|
250
|
+
|
|
251
|
+
@Controller("webhooks")
|
|
252
|
+
export class WebhooksController {
|
|
253
|
+
constructor(@Inject(RETAINFUL) private readonly retainful: RetainfulClient) {}
|
|
254
|
+
|
|
255
|
+
@Post()
|
|
256
|
+
async handle(
|
|
257
|
+
@Headers("x-shopify-topic") topic: string,
|
|
258
|
+
@Body() shop: any
|
|
259
|
+
) {
|
|
260
|
+
try {
|
|
261
|
+
if (topic === "app/uninstalled") {
|
|
262
|
+
await this.retainful.events.uninstalled({
|
|
263
|
+
email: shop.email,
|
|
264
|
+
shopDomain: shop.myshopify_domain,
|
|
265
|
+
shopDetails: {
|
|
266
|
+
name: shop.name,
|
|
267
|
+
ownerName: shop.shop_owner,
|
|
268
|
+
plan: shop.plan_name,
|
|
269
|
+
country: shop.country_name,
|
|
270
|
+
currency: shop.currency,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (error instanceof RetainfulApiError) {
|
|
276
|
+
console.error(`Retainful error ${error.statusCode}:`, error.statusText);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { ok: true };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Retry Configuration
|
|
288
|
+
|
|
289
|
+
By default the SDK retries failed requests (status codes `429`, `503`, `504`) up to **3 times** with exponential backoff.
|
|
290
|
+
|
|
291
|
+
You can override this:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
const retainful = new RetainfulClient(
|
|
295
|
+
{ apiKey: "..." },
|
|
296
|
+
{ appName: "My App", appSlug: "my-app" },
|
|
297
|
+
{
|
|
298
|
+
retryCodes: [429, 503],
|
|
299
|
+
numRetries: 5,
|
|
300
|
+
maxInterval: 30, // seconds
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Payload Reference
|
|
308
|
+
|
|
309
|
+
### `installed(payload)` / `uninstalled(payload)`
|
|
310
|
+
|
|
311
|
+
| Field | Type | Required | Description |
|
|
312
|
+
|---|---|---|---|
|
|
313
|
+
| `email` | `string` | ✅ | Store owner email — contact identifier in Retainful |
|
|
314
|
+
| `shopDomain` | `string` | ✅ | Myshopify domain, e.g. `my-store.myshopify.com` |
|
|
315
|
+
| `shopDetails` | `ShopDetails` | ✅ | Full shop object from Shopify |
|
|
316
|
+
|
|
317
|
+
### `ShopDetails`
|
|
318
|
+
|
|
319
|
+
| Field | Type | Description |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| `name` | `string` | Store display name |
|
|
322
|
+
| `ownerName` | `string` | Store owner's full name |
|
|
323
|
+
| `plan` | `string` | Shopify plan, e.g. `"basic"`, `"shopify"`, `"advanced"` |
|
|
324
|
+
| `country` | `string` | Store country |
|
|
325
|
+
| `currency` | `string` | Store currency, e.g. `"USD"` |
|
|
326
|
+
| `timezone` | `string` | Store timezone |
|
|
327
|
+
| `phone` | `string \| null` | Owner phone number |
|
|
328
|
+
|
|
329
|
+
All `ShopDetails` fields are optional. You can also pass any extra Shopify fields — the type allows `[key: string]: unknown`.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## What gets sent over the wire
|
|
334
|
+
|
|
335
|
+
Every event POST to `https://api.retainful.net/api/v1/events` includes:
|
|
336
|
+
|
|
337
|
+
**Headers:**
|
|
338
|
+
```
|
|
339
|
+
Retainful-Api-Key: YOUR_API_KEY
|
|
340
|
+
Content-Type: application/json
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Body:** the SDK maps the ergonomic `installed()` / `uninstalled()` payload onto
|
|
344
|
+
Retainful's `eventName` / `contact` / `eventData` / `uniqueIdentifier` format:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"eventName": "Order Tracking Pro installed",
|
|
349
|
+
"description": "App installed",
|
|
350
|
+
"contact": {
|
|
351
|
+
"email": "owner@my-store.com",
|
|
352
|
+
"first_name": "Jane",
|
|
353
|
+
"last_name": "Doe",
|
|
354
|
+
"phone": "+1234567890",
|
|
355
|
+
"email_opt_in": "SUBSCRIBED"
|
|
356
|
+
},
|
|
357
|
+
"eventData": {
|
|
358
|
+
"eventType": "installed",
|
|
359
|
+
"appName": "Order Tracking Pro",
|
|
360
|
+
"appSlug": "order-tracking-pro",
|
|
361
|
+
"storeDomain": "my-store.myshopify.com",
|
|
362
|
+
"countryCode": "United States",
|
|
363
|
+
"name": "My Store",
|
|
364
|
+
"plan": "basic",
|
|
365
|
+
"currency": "USD",
|
|
366
|
+
"timezone": "America/New_York"
|
|
367
|
+
},
|
|
368
|
+
"uniqueIdentifier": "my-store.myshopify.com-installed"
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Mapping rules:**
|
|
373
|
+
|
|
374
|
+
| Wire field | Source |
|
|
375
|
+
|---|---|
|
|
376
|
+
| `eventName` | `"<appName> installed"` / `"<appName> uninstalled"` |
|
|
377
|
+
| `contact.email` | `payload.email` |
|
|
378
|
+
| `contact.first_name` / `last_name` | split from `shopDetails.ownerName` (override with `firstName` / `lastName`) |
|
|
379
|
+
| `contact.email_opt_in` | `payload.emailOptIn`, default `"SUBSCRIBED"` |
|
|
380
|
+
| `eventData` | `eventType`, `appName`, `appSlug`, `storeDomain`, `countryCode` + all other `shopDetails` + any `payload.eventData` |
|
|
381
|
+
| `uniqueIdentifier` | `payload.uniqueIdentifier`, default `"<shopDomain>-<installed\|uninstalled>"` |
|
|
382
|
+
|
|
383
|
+
Need full control? Use the escape hatch — `retainful.events.track(rawEvent)` posts an
|
|
384
|
+
`{ eventName, description, contact, eventData, uniqueIdentifier }` body verbatim with no mapping.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Error Handling
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { RetainfulApiError, RetainfulConfigError } from "@yuko-app/retainful-shopify-sdk";
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
await retainful.events.installed({ ... });
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (error instanceof RetainfulApiError) {
|
|
397
|
+
console.error(error.statusCode); // e.g. 401, 403, 422
|
|
398
|
+
console.error(error.statusText); // e.g. "Unauthorized"
|
|
399
|
+
console.error(error.responseData); // Full response body
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (error instanceof RetainfulConfigError) {
|
|
403
|
+
// Thrown at construction time if apiKey is missing
|
|
404
|
+
console.error(error.message);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Multiple Apps (Monorepo)
|
|
412
|
+
|
|
413
|
+
Each Shopify app creates its own client with its own `appName` / `appSlug`.
|
|
414
|
+
They all share the **same Retainful credentials** (scoped to Yuko Apps org via the API key).
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// apps/order-tracking/lib/retainful.ts
|
|
418
|
+
export const retainful = new RetainfulClient(
|
|
419
|
+
{ apiKey: process.env.RETAINFUL_API_KEY! },
|
|
420
|
+
{ appName: "Order Tracking Pro", appSlug: "order-tracking-pro" }
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// apps/reviews/lib/retainful.ts
|
|
424
|
+
export const retainful = new RetainfulClient(
|
|
425
|
+
{ apiKey: process.env.RETAINFUL_API_KEY! },
|
|
426
|
+
{ appName: "Reviews & Ratings", appSlug: "reviews-ratings" }
|
|
427
|
+
);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Building
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
npm run build # compiles to /dist
|
|
436
|
+
npm run dev # watch mode
|
|
437
|
+
```
|
|
438
|
+
# Retainful-Shopify-apps-sdk-beta
|
|
439
|
+
# Retainful-Shopify-apps-sdk
|
|
440
|
+
# Retainful-Shopify-apps-sdk
|
|
441
|
+
# Retainful-Shopify-apps-sdk
|
|
442
|
+
# Retainful-Shopify-apps-sdk
|
|
443
|
+
# Retainful-Shopify-apps-sdk
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RetainfulSession } from "../session";
|
|
2
|
+
import { AppEventPayload, RetainfulEventRequest, RetainfulEventResponse, ShopifyAppConfig } from "../types/index";
|
|
3
|
+
/**
|
|
4
|
+
* EventsApi
|
|
5
|
+
*
|
|
6
|
+
* Sends Shopify app lifecycle events (install / uninstall) to Retainful.
|
|
7
|
+
* Automatically injects app_name and app_slug from the ShopifyAppConfig
|
|
8
|
+
* so you never have to repeat them at the call site.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const events = new EventsApi(session, { appName: "Order Tracking Pro", appSlug: "order-tracking-pro" });
|
|
12
|
+
* await events.installed({ email, shopDomain, shopDetails });
|
|
13
|
+
* await events.uninstalled({ email, shopDomain, shopDetails });
|
|
14
|
+
*/
|
|
15
|
+
export declare class EventsApi {
|
|
16
|
+
private readonly session;
|
|
17
|
+
private readonly appConfig;
|
|
18
|
+
constructor(session: RetainfulSession, appConfig: ShopifyAppConfig);
|
|
19
|
+
/**
|
|
20
|
+
* Track an app install event.
|
|
21
|
+
* Call this in your Shopify app's install webhook or auth callback.
|
|
22
|
+
*/
|
|
23
|
+
installed(payload: Omit<AppEventPayload, "eventType">): Promise<RetainfulEventResponse>;
|
|
24
|
+
/**
|
|
25
|
+
* Track an app uninstall event.
|
|
26
|
+
* Call this in your Shopify app/uninstalled webhook handler.
|
|
27
|
+
*/
|
|
28
|
+
uninstalled(payload: Omit<AppEventPayload, "eventType">): Promise<RetainfulEventResponse>;
|
|
29
|
+
/**
|
|
30
|
+
* Send any lifecycle event directly (lower-level).
|
|
31
|
+
* Use installed() / uninstalled() for most cases.
|
|
32
|
+
*
|
|
33
|
+
* Maps the ergonomic SDK payload onto Retainful's wire format
|
|
34
|
+
* (eventName / contact / eventData / uniqueIdentifier).
|
|
35
|
+
*/
|
|
36
|
+
sendEvent(payload: AppEventPayload): Promise<RetainfulEventResponse>;
|
|
37
|
+
/**
|
|
38
|
+
* Send a fully-formed event to Retainful with no mapping.
|
|
39
|
+
* Escape hatch for events that don't fit the install/uninstall shape.
|
|
40
|
+
*/
|
|
41
|
+
track(event: RetainfulEventRequest): Promise<RetainfulEventResponse>;
|
|
42
|
+
/** Translate an AppEventPayload into the Retainful wire format. */
|
|
43
|
+
private buildEvent;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/api/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAIxB;;;;;;;;;;;GAWG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;gBAEjC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB;IAKlE;;;OAGG;IACG,SAAS,CACb,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAC1C,OAAO,CAAC,sBAAsB,CAAC;IAIlC;;;OAGG;IACG,WAAW,CACf,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAC1C,OAAO,CAAC,sBAAsB,CAAC;IAIlC;;;;;;OAMG;IACG,SAAS,CACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,sBAAsB,CAAC;IAIlC;;;OAGG;IACG,KAAK,CACT,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAOlC,mEAAmE;IACnE,OAAO,CAAC,UAAU;CAsCnB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventsApi = void 0;
|
|
4
|
+
const EVENTS_ENDPOINT = "/api/v1/events";
|
|
5
|
+
/**
|
|
6
|
+
* EventsApi
|
|
7
|
+
*
|
|
8
|
+
* Sends Shopify app lifecycle events (install / uninstall) to Retainful.
|
|
9
|
+
* Automatically injects app_name and app_slug from the ShopifyAppConfig
|
|
10
|
+
* so you never have to repeat them at the call site.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const events = new EventsApi(session, { appName: "Order Tracking Pro", appSlug: "order-tracking-pro" });
|
|
14
|
+
* await events.installed({ email, shopDomain, shopDetails });
|
|
15
|
+
* await events.uninstalled({ email, shopDomain, shopDetails });
|
|
16
|
+
*/
|
|
17
|
+
class EventsApi {
|
|
18
|
+
constructor(session, appConfig) {
|
|
19
|
+
this.session = session;
|
|
20
|
+
this.appConfig = appConfig;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Track an app install event.
|
|
24
|
+
* Call this in your Shopify app's install webhook or auth callback.
|
|
25
|
+
*/
|
|
26
|
+
async installed(payload) {
|
|
27
|
+
return this.sendEvent({ ...payload, eventType: "app/installed" });
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Track an app uninstall event.
|
|
31
|
+
* Call this in your Shopify app/uninstalled webhook handler.
|
|
32
|
+
*/
|
|
33
|
+
async uninstalled(payload) {
|
|
34
|
+
return this.sendEvent({ ...payload, eventType: "app/uninstalled" });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Send any lifecycle event directly (lower-level).
|
|
38
|
+
* Use installed() / uninstalled() for most cases.
|
|
39
|
+
*
|
|
40
|
+
* Maps the ergonomic SDK payload onto Retainful's wire format
|
|
41
|
+
* (eventName / contact / eventData / uniqueIdentifier).
|
|
42
|
+
*/
|
|
43
|
+
async sendEvent(payload) {
|
|
44
|
+
return this.track(this.buildEvent(payload));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Send a fully-formed event to Retainful with no mapping.
|
|
48
|
+
* Escape hatch for events that don't fit the install/uninstall shape.
|
|
49
|
+
*/
|
|
50
|
+
async track(event) {
|
|
51
|
+
return this.session.post(EVENTS_ENDPOINT, event);
|
|
52
|
+
}
|
|
53
|
+
/** Translate an AppEventPayload into the Retainful wire format. */
|
|
54
|
+
buildEvent(payload) {
|
|
55
|
+
var _a, _b, _c, _d, _e;
|
|
56
|
+
// "app/installed" -> "installed", "app/uninstalled" -> "uninstalled"
|
|
57
|
+
const shortType = payload.eventType.replace(/^app\//, "");
|
|
58
|
+
const { ownerName, phone, country, ...restDetails } = payload.shopDetails;
|
|
59
|
+
const nameTokens = (ownerName !== null && ownerName !== void 0 ? ownerName : "").trim().split(/\s+/).filter(Boolean);
|
|
60
|
+
const firstName = (_a = payload.firstName) !== null && _a !== void 0 ? _a : nameTokens[0];
|
|
61
|
+
const lastName = (_b = payload.lastName) !== null && _b !== void 0 ? _b : (nameTokens.slice(1).join(" ") || undefined);
|
|
62
|
+
return {
|
|
63
|
+
eventName: `${this.appConfig.appName} ${shortType}`,
|
|
64
|
+
description: (_c = payload.description) !== null && _c !== void 0 ? _c : `App ${shortType}`,
|
|
65
|
+
contact: {
|
|
66
|
+
email: payload.email,
|
|
67
|
+
first_name: firstName,
|
|
68
|
+
last_name: lastName,
|
|
69
|
+
phone: phone !== null && phone !== void 0 ? phone : undefined,
|
|
70
|
+
email_opt_in: (_d = payload.emailOptIn) !== null && _d !== void 0 ? _d : "SUBSCRIBED",
|
|
71
|
+
},
|
|
72
|
+
eventData: {
|
|
73
|
+
eventType: shortType,
|
|
74
|
+
appName: this.appConfig.appName,
|
|
75
|
+
appSlug: this.appConfig.appSlug,
|
|
76
|
+
storeDomain: payload.shopDomain,
|
|
77
|
+
countryCode: country,
|
|
78
|
+
...restDetails,
|
|
79
|
+
...payload.eventData,
|
|
80
|
+
},
|
|
81
|
+
uniqueIdentifier: (_e = payload.uniqueIdentifier) !== null && _e !== void 0 ? _e : `${payload.shopDomain}-${shortType}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.EventsApi = EventsApi;
|
|
86
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/api/events.ts"],"names":[],"mappings":";;;AAQA,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,MAAa,SAAS;IAIpB,YAAY,OAAyB,EAAE,SAA2B;QAChE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,SAAS,CACb,OAA2C;QAE3C,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC;IACpE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CACf,OAA2C;QAE3C,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CACb,OAAwB;QAExB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CACT,KAA4B;QAE5B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CACtB,eAAe,EACf,KAAK,CACN,CAAC;IACJ,CAAC;IAED,mEAAmE;IAC3D,UAAU,CAAC,OAAwB;;QACzC,qEAAqE;QACrE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAE1D,MAAM,EACJ,SAAS,EACT,KAAK,EACL,OAAO,EACP,GAAG,WAAW,EACf,GAAG,OAAO,CAAC,WAAW,CAAC;QAExB,MAAM,UAAU,GAAG,CAAC,SAAS,aAAT,SAAS,cAAT,SAAS,GAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzE,MAAM,SAAS,GAAG,MAAA,OAAO,CAAC,SAAS,mCAAI,UAAU,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,MAAA,OAAO,CAAC,QAAQ,mCAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,CAAC;QAElF,OAAO;YACL,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,SAAS,EAAE;YACnD,WAAW,EAAE,MAAA,OAAO,CAAC,WAAW,mCAAI,OAAO,SAAS,EAAE;YACtD,OAAO,EAAE;gBACP,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,QAAQ;gBACnB,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,SAAS;gBACzB,YAAY,EAAE,MAAA,OAAO,CAAC,UAAU,mCAAI,YAAY;aACjD;YACD,SAAS,EAAE;gBACT,SAAS,EAAE,SAAS;gBACpB,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO;gBAC/B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO;gBAC/B,WAAW,EAAE,OAAO,CAAC,UAAU;gBAC/B,WAAW,EAAE,OAAO;gBACpB,GAAG,WAAW;gBACd,GAAG,OAAO,CAAC,SAAS;aACrB;YACD,gBAAgB,EACd,MAAA,OAAO,CAAC,gBAAgB,mCAAI,GAAG,OAAO,CAAC,UAAU,IAAI,SAAS,EAAE;SACnE,CAAC;IACJ,CAAC;CACF;AA9FD,8BA8FC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EventsApi } from "./api/events";
|
|
2
|
+
import { RetainfulSessionConfig, ShopifyAppConfig, RetryConfig } from "./types/index";
|
|
3
|
+
export { RetainfulApiError, RetainfulConfigError } from "./types/index";
|
|
4
|
+
export type { RetainfulSessionConfig, ShopifyAppConfig, RetryConfig, AppEventPayload, ShopDetails, RetainfulEventType, RetainfulEventResponse, RetainfulEventRequest, RetainfulContact, EmailOptInStatus, } from "./types/index";
|
|
5
|
+
/**
|
|
6
|
+
* RetainfulClient
|
|
7
|
+
*
|
|
8
|
+
* The main entry point for the Retainful SDK.
|
|
9
|
+
* Instantiate once per Shopify app and reuse across your request handlers.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { RetainfulClient } from "@akashm-cartrabbit/retainful-shopify-sdk";
|
|
13
|
+
*
|
|
14
|
+
* const retainful = new RetainfulClient(
|
|
15
|
+
* { apiKey: process.env.RETAINFUL_API_KEY },
|
|
16
|
+
* { appName: "Order Tracking Pro", appSlug: "order-tracking-pro" }
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* // On install:
|
|
20
|
+
* await retainful.events.installed({ email, shopDomain, shopDetails });
|
|
21
|
+
*
|
|
22
|
+
* // On uninstall:
|
|
23
|
+
* await retainful.events.uninstalled({ email, shopDomain, shopDetails });
|
|
24
|
+
*/
|
|
25
|
+
export declare class RetainfulClient {
|
|
26
|
+
readonly events: EventsApi;
|
|
27
|
+
private readonly session;
|
|
28
|
+
constructor(sessionConfig: RetainfulSessionConfig, appConfig: ShopifyAppConfig, retryConfig?: RetryConfig);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,WAAW,EACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,eAAe;IAC1B,SAAgB,MAAM,EAAE,SAAS,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;gBAGzC,aAAa,EAAE,sBAAsB,EACrC,SAAS,EAAE,gBAAgB,EAC3B,WAAW,CAAC,EAAE,WAAW;CAK5B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetainfulClient = exports.RetainfulConfigError = exports.RetainfulApiError = void 0;
|
|
4
|
+
const session_1 = require("./session");
|
|
5
|
+
const events_1 = require("./api/events");
|
|
6
|
+
var index_1 = require("./types/index");
|
|
7
|
+
Object.defineProperty(exports, "RetainfulApiError", { enumerable: true, get: function () { return index_1.RetainfulApiError; } });
|
|
8
|
+
Object.defineProperty(exports, "RetainfulConfigError", { enumerable: true, get: function () { return index_1.RetainfulConfigError; } });
|
|
9
|
+
/**
|
|
10
|
+
* RetainfulClient
|
|
11
|
+
*
|
|
12
|
+
* The main entry point for the Retainful SDK.
|
|
13
|
+
* Instantiate once per Shopify app and reuse across your request handlers.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* import { RetainfulClient } from "@akashm-cartrabbit/retainful-shopify-sdk";
|
|
17
|
+
*
|
|
18
|
+
* const retainful = new RetainfulClient(
|
|
19
|
+
* { apiKey: process.env.RETAINFUL_API_KEY },
|
|
20
|
+
* { appName: "Order Tracking Pro", appSlug: "order-tracking-pro" }
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* // On install:
|
|
24
|
+
* await retainful.events.installed({ email, shopDomain, shopDetails });
|
|
25
|
+
*
|
|
26
|
+
* // On uninstall:
|
|
27
|
+
* await retainful.events.uninstalled({ email, shopDomain, shopDetails });
|
|
28
|
+
*/
|
|
29
|
+
class RetainfulClient {
|
|
30
|
+
constructor(sessionConfig, appConfig, retryConfig) {
|
|
31
|
+
this.session = new session_1.RetainfulSession(sessionConfig, retryConfig);
|
|
32
|
+
this.events = new events_1.EventsApi(this.session, appConfig);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.RetainfulClient = RetainfulClient;
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAA6C;AAC7C,yCAAyC;AAOzC,uCAAwE;AAA/D,0GAAA,iBAAiB,OAAA;AAAE,6GAAA,oBAAoB,OAAA;AAchD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,eAAe;IAI1B,YACE,aAAqC,EACrC,SAA2B,EAC3B,WAAyB;QAEzB,IAAI,CAAC,OAAO,GAAG,IAAI,0BAAgB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAS,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvD,CAAC;CACF;AAZD,0CAYC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RetainfulSessionConfig, RetryConfig } from "./types/index";
|
|
2
|
+
export declare class RetainfulSession {
|
|
3
|
+
private readonly client;
|
|
4
|
+
private readonly retryConfig?;
|
|
5
|
+
constructor(config: RetainfulSessionConfig, retryConfig?: RetryConfig);
|
|
6
|
+
post<TBody, TResponse>(path: string, body: TBody): Promise<TResponse>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EAGtB,WAAW,EACZ,MAAM,eAAe,CAAC;AAKvB,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAc;gBAE/B,MAAM,EAAE,sBAAsB,EAAE,WAAW,CAAC,EAAE,WAAW;IAkB/D,IAAI,CAAC,KAAK,EAAE,SAAS,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,SAAS,CAAC;CAqBtB"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RetainfulSession = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const index_1 = require("./types/index");
|
|
9
|
+
const retry_1 = require("./utils/retry");
|
|
10
|
+
const BASE_URL = "https://api.retainful.net";
|
|
11
|
+
class RetainfulSession {
|
|
12
|
+
constructor(config, retryConfig) {
|
|
13
|
+
if (!config.apiKey) {
|
|
14
|
+
throw new index_1.RetainfulConfigError("Retainful API key is required. Get it from Settings → API Keys.");
|
|
15
|
+
}
|
|
16
|
+
this.retryConfig = retryConfig;
|
|
17
|
+
this.client = axios_1.default.create({
|
|
18
|
+
baseURL: BASE_URL,
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
"Retainful-Api-Key": config.apiKey,
|
|
22
|
+
},
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async post(path, body) {
|
|
27
|
+
return (0, retry_1.withRetry)(async () => {
|
|
28
|
+
try {
|
|
29
|
+
const response = await this.client.post(path, body);
|
|
30
|
+
return response.data;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const axiosError = error;
|
|
34
|
+
if (axiosError.response) {
|
|
35
|
+
throw new index_1.RetainfulApiError(axiosError.response.status, axiosError.response.statusText, axiosError.response.data);
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}, this.retryConfig);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.RetainfulSession = RetainfulSession;
|
|
43
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAwE;AACxE,yCAKuB;AACvB,yCAA0C;AAE1C,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAE7C,MAAa,gBAAgB;IAI3B,YAAY,MAA8B,EAAE,WAAyB;QACnE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,4BAAoB,CAC5B,iEAAiE,CAClE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAE/B,IAAI,CAAC,MAAM,GAAG,eAAK,CAAC,MAAM,CAAC;YACzB,OAAO,EAAE,QAAQ;YACjB,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,mBAAmB,EAAE,MAAM,CAAC,MAAM;aACnC;YACD,OAAO,EAAE,KAAM;SAChB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CACR,IAAY,EACZ,IAAW;QAEX,OAAO,IAAA,iBAAS,EAAC,KAAK,IAAI,EAAE;YAC1B,IAAI,CAAC;gBACH,MAAM,QAAQ,GAA6B,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAC/D,IAAI,EACJ,IAAI,CACL,CAAC;gBACF,OAAO,QAAQ,CAAC,IAAI,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,UAAU,GAAG,KAAmB,CAAC;gBACvC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;oBACxB,MAAM,IAAI,yBAAiB,CACzB,UAAU,CAAC,QAAQ,CAAC,MAAM,EAC1B,UAAU,CAAC,QAAQ,CAAC,UAAU,EAC9B,UAAU,CAAC,QAAQ,CAAC,IAAI,CACzB,CAAC;gBACJ,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC;CACF;AA9CD,4CA8CC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export interface RetainfulSessionConfig {
|
|
2
|
+
/** Your Retainful API Key (from Settings → API Keys) */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ShopifyAppConfig {
|
|
6
|
+
/** Human-readable name of your Shopify app, e.g. "Order Tracking Pro" */
|
|
7
|
+
appName: string;
|
|
8
|
+
/** URL-safe slug for your app, e.g. "order-tracking-pro" */
|
|
9
|
+
appSlug: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RetryConfig {
|
|
12
|
+
/** HTTP status codes to retry on. Default: [429, 503, 504] */
|
|
13
|
+
retryCodes?: number[];
|
|
14
|
+
/** Number of retry attempts. Default: 3 */
|
|
15
|
+
numRetries?: number;
|
|
16
|
+
/** Maximum backoff interval in seconds. Default: 60 */
|
|
17
|
+
maxInterval?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ShopDetails {
|
|
20
|
+
/** Shopify store name */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** Store owner's name */
|
|
23
|
+
ownerName?: string;
|
|
24
|
+
/** Store owner's phone */
|
|
25
|
+
phone?: string | null;
|
|
26
|
+
/** Shopify plan name, e.g. "basic", "shopify", "advanced" */
|
|
27
|
+
plan?: string;
|
|
28
|
+
/** Store's primary country */
|
|
29
|
+
country?: string;
|
|
30
|
+
/** Store currency, e.g. "USD" */
|
|
31
|
+
currency?: string;
|
|
32
|
+
/** Store's timezone */
|
|
33
|
+
timezone?: string;
|
|
34
|
+
/** Any extra Shopify store fields you want to forward */
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
export type RetainfulEventType = "app/installed" | "app/uninstalled";
|
|
38
|
+
/** Email subscription status as understood by Retainful. */
|
|
39
|
+
export type EmailOptInStatus = "SUBSCRIBED" | "UNSUBSCRIBED" | (string & {});
|
|
40
|
+
export interface AppEventPayload {
|
|
41
|
+
/** Type of lifecycle event */
|
|
42
|
+
eventType: RetainfulEventType;
|
|
43
|
+
/** Store owner / admin email — used as the contact identifier in Retainful */
|
|
44
|
+
email: string;
|
|
45
|
+
/** Shopify shop domain, e.g. "my-store.myshopify.com" — unique identifier */
|
|
46
|
+
shopDomain: string;
|
|
47
|
+
/** Full shop details object from Shopify */
|
|
48
|
+
shopDetails: ShopDetails;
|
|
49
|
+
/** Contact first name. Defaults to the first token of shopDetails.ownerName. */
|
|
50
|
+
firstName?: string;
|
|
51
|
+
/** Contact last name. Defaults to the remaining tokens of shopDetails.ownerName. */
|
|
52
|
+
lastName?: string;
|
|
53
|
+
/** Email subscription status. Default: "SUBSCRIBED" */
|
|
54
|
+
emailOptIn?: EmailOptInStatus;
|
|
55
|
+
/** Human-readable description. Default: "App installed" / "App uninstalled". */
|
|
56
|
+
description?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Idempotency key for this event.
|
|
59
|
+
* Default: `${shopDomain}-${installed|uninstalled}`.
|
|
60
|
+
*/
|
|
61
|
+
uniqueIdentifier?: string;
|
|
62
|
+
/** Extra fields merged into the eventData object sent to Retainful. */
|
|
63
|
+
eventData?: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
export interface RetainfulContact {
|
|
66
|
+
email: string;
|
|
67
|
+
first_name?: string;
|
|
68
|
+
last_name?: string;
|
|
69
|
+
phone?: string | null;
|
|
70
|
+
email_opt_in?: EmailOptInStatus;
|
|
71
|
+
}
|
|
72
|
+
export interface RetainfulEventRequest {
|
|
73
|
+
/** Display name of the event, e.g. "Order Tracking Pro installed". */
|
|
74
|
+
eventName: string;
|
|
75
|
+
/** Human-readable description of what happened. */
|
|
76
|
+
description?: string;
|
|
77
|
+
/** The contact this event is associated with. */
|
|
78
|
+
contact: RetainfulContact;
|
|
79
|
+
/** Arbitrary structured data attached to the event. */
|
|
80
|
+
eventData: Record<string, unknown>;
|
|
81
|
+
/** Idempotency key — dedupes repeated deliveries of the same event. */
|
|
82
|
+
uniqueIdentifier: string;
|
|
83
|
+
}
|
|
84
|
+
export interface RetainfulEventResponse {
|
|
85
|
+
success: boolean;
|
|
86
|
+
message?: string;
|
|
87
|
+
[key: string]: unknown;
|
|
88
|
+
}
|
|
89
|
+
export declare class RetainfulApiError extends Error {
|
|
90
|
+
readonly statusCode: number;
|
|
91
|
+
readonly statusText: string;
|
|
92
|
+
readonly responseData?: unknown;
|
|
93
|
+
constructor(statusCode: number, statusText: string, responseData?: unknown);
|
|
94
|
+
}
|
|
95
|
+
export declare class RetainfulConfigError extends Error {
|
|
96
|
+
constructor(message: string);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────
|
|
3
|
+
// Auth & Session Types
|
|
4
|
+
// ─────────────────────────────────────────────
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RetainfulConfigError = exports.RetainfulApiError = void 0;
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
// Errors
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
class RetainfulApiError extends Error {
|
|
11
|
+
constructor(statusCode, statusText, responseData) {
|
|
12
|
+
super(`Retainful API Error ${statusCode}: ${statusText}`);
|
|
13
|
+
this.name = "RetainfulApiError";
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.statusText = statusText;
|
|
16
|
+
this.responseData = responseData;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.RetainfulApiError = RetainfulApiError;
|
|
20
|
+
class RetainfulConfigError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "RetainfulConfigError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.RetainfulConfigError = RetainfulConfigError;
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":";AAAA,gDAAgD;AAChD,uBAAuB;AACvB,gDAAgD;;;AAgIhD,gDAAgD;AAChD,SAAS;AACT,gDAAgD;AAEhD,MAAa,iBAAkB,SAAQ,KAAK;IAK1C,YAAY,UAAkB,EAAE,UAAkB,EAAE,YAAsB;QACxE,KAAK,CAAC,uBAAuB,UAAU,KAAK,UAAU,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;CACF;AAZD,8CAYC;AAED,MAAa,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AALD,oDAKC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RetryConfig } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps an async function with retry logic using exponential backoff.
|
|
4
|
+
* Retries only on configured HTTP status codes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>;
|
|
7
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAyBvC;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CA+BZ"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withRetry = withRetry;
|
|
4
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
5
|
+
retryCodes: [429, 503, 504],
|
|
6
|
+
numRetries: 3,
|
|
7
|
+
maxInterval: 60,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Sleep for a given number of milliseconds.
|
|
11
|
+
*/
|
|
12
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
/**
|
|
14
|
+
* Calculate exponential backoff delay with jitter.
|
|
15
|
+
* Formula: min(maxInterval, (2^attempt) * 1000) + random jitter
|
|
16
|
+
*/
|
|
17
|
+
const getBackoffDelay = (attempt, maxInterval) => {
|
|
18
|
+
const exponential = Math.pow(2, attempt) * 1000;
|
|
19
|
+
const capped = Math.min(maxInterval * 1000, exponential);
|
|
20
|
+
const jitter = Math.random() * 1000;
|
|
21
|
+
return capped + jitter;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Wraps an async function with retry logic using exponential backoff.
|
|
25
|
+
* Retries only on configured HTTP status codes.
|
|
26
|
+
*/
|
|
27
|
+
async function withRetry(fn, config) {
|
|
28
|
+
const { retryCodes, numRetries, maxInterval } = {
|
|
29
|
+
...DEFAULT_RETRY_CONFIG,
|
|
30
|
+
...config,
|
|
31
|
+
};
|
|
32
|
+
let lastError;
|
|
33
|
+
for (let attempt = 0; attempt <= numRetries; attempt++) {
|
|
34
|
+
try {
|
|
35
|
+
return await fn();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
lastError = error;
|
|
39
|
+
// Only retry on specific HTTP status codes
|
|
40
|
+
const statusCode = extractStatusCode(error);
|
|
41
|
+
const shouldRetry = attempt < numRetries &&
|
|
42
|
+
statusCode !== null &&
|
|
43
|
+
retryCodes.includes(statusCode);
|
|
44
|
+
if (!shouldRetry) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
const delay = getBackoffDelay(attempt, maxInterval);
|
|
48
|
+
await sleep(delay);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw lastError;
|
|
52
|
+
}
|
|
53
|
+
function extractStatusCode(error) {
|
|
54
|
+
if (error &&
|
|
55
|
+
typeof error === "object" &&
|
|
56
|
+
"response" in error &&
|
|
57
|
+
error.response &&
|
|
58
|
+
typeof error.response === "object" &&
|
|
59
|
+
"status" in error.response &&
|
|
60
|
+
typeof error.response.status === "number") {
|
|
61
|
+
return error.response.status;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":";;AA6BA,8BAkCC;AA7DD,MAAM,oBAAoB,GAA0B;IAClD,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IAC3B,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,EAAE;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEpD;;;GAGG;AACH,MAAM,eAAe,GAAG,CAAC,OAAe,EAAE,WAAmB,EAAU,EAAE;IACvE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,OAAO,MAAM,GAAG,MAAM,CAAC;AACzB,CAAC,CAAC;AAEF;;;GAGG;AACI,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,MAAoB;IAEpB,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG;QAC9C,GAAG,oBAAoB;QACvB,GAAG,MAAM;KACV,CAAC;IAEF,IAAI,SAAkB,CAAC;IAEvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,SAAS,GAAG,KAAK,CAAC;YAElB,2CAA2C;YAC3C,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM,WAAW,GACf,OAAO,GAAG,UAAU;gBACpB,UAAU,KAAK,IAAI;gBACnB,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YAElC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YACpD,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,MAAM,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,IACE,KAAK;QACL,OAAO,KAAK,KAAK,QAAQ;QACzB,UAAU,IAAI,KAAK;QACnB,KAAK,CAAC,QAAQ;QACd,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,QAAQ,IAAI,KAAK,CAAC,QAAQ;QAC1B,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,QAAQ,EACzC,CAAC;QACD,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "retainful-shopify-sdk",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Retainful SDK for Shopify apps — Shopify lifecycle event tracking",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"trigger": "npm run build && node scripts/trigger-event.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"retainful",
|
|
18
|
+
"shopify",
|
|
19
|
+
"yuko-apps",
|
|
20
|
+
"marketing-automation"
|
|
21
|
+
],
|
|
22
|
+
"author": "Shopify Apps",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"axios": "^1.6.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/yuko-app/Retainful-Shopify-apps-sdk.git"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"registry": "https://registry.npmjs.org",
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|