@progus/connector 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -2
- package/dist/index.cjs +118 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +115 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,39 @@ await connector.trackSubscriptionUpdated({
|
|
|
37
37
|
await connector.trackUninstall({ shopDomain: session.shop });
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Prompt: Partner program implementation (copy/paste)
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
You are integrating Progus Partner Program using @progus/connector in a Shopify app.
|
|
44
|
+
|
|
45
|
+
Implement these pieces:
|
|
46
|
+
1) Server connector:
|
|
47
|
+
- createProgusConnector({ appKey, PARTNERS_API_URL, PARTNERS_SECRET_KEY, PARTNERS_API_KEY })
|
|
48
|
+
- expose getPartnersConnector() + isPartnersConnectorConfigured()
|
|
49
|
+
|
|
50
|
+
2) Install tracking:
|
|
51
|
+
- after app install, call trackInstall({ shopDomain, partnerId })
|
|
52
|
+
- partnerId source: read cookie "progus_partner_id" on progus.com
|
|
53
|
+
- cookie must be JS-readable (HttpOnly=false) and cross-site (SameSite=None; Secure)
|
|
54
|
+
- if partnerId is missing, skip tracking
|
|
55
|
+
|
|
56
|
+
3) Partner ID assignment UI:
|
|
57
|
+
- save partnerId from merchant input (Partner ID field)
|
|
58
|
+
- call assignPartnerId({ shopDomain, partnerId }) on save
|
|
59
|
+
- lock the field after successful save
|
|
60
|
+
- optionally check partnerId on load with checkPartnerId({ shopDomain })
|
|
61
|
+
|
|
62
|
+
4) Subscription tracking:
|
|
63
|
+
- on app_subscriptions/update webhook, send trackSubscriptionUpdated
|
|
64
|
+
- payload must include: subscriptionId (digits), subscriptionPrice (number), subscriptionPeriod ("EVERY_30_DAYS" or "ANNUAL"), subscriptionStatus, subscriptionName
|
|
65
|
+
- if webhook payload is missing price/interval, fetch details via Admin GraphQL node(id) -> AppSubscription -> lineItems.plan.pricingDetails
|
|
66
|
+
|
|
67
|
+
5) Uninstall tracking:
|
|
68
|
+
- on app/uninstalled webhook, call trackUninstall({ shopDomain })
|
|
69
|
+
|
|
70
|
+
Return errors gracefully; do not break webhooks if partner tracking fails.
|
|
71
|
+
```
|
|
72
|
+
|
|
40
73
|
## Partner ID assignment
|
|
41
74
|
|
|
42
75
|
```ts
|
|
@@ -58,9 +91,25 @@ import { signPayload } from "@progus/connector";
|
|
|
58
91
|
const signature = signPayload(JSON.stringify({ test: true }), process.env.PARTNERS_SECRET_KEY!);
|
|
59
92
|
```
|
|
60
93
|
|
|
61
|
-
##
|
|
94
|
+
## Cross-sell recommendations (server-side)
|
|
95
|
+
|
|
96
|
+
Use the connector to fetch and filter the Progus apps catalog on the backend,
|
|
97
|
+
then pass the list to your frontend UI.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { getCrossSellOffersFromApi } from "@progus/connector";
|
|
101
|
+
|
|
102
|
+
const recommendations = await getCrossSellOffersFromApi({
|
|
103
|
+
appName: "progus-store-locator",
|
|
104
|
+
limit: 3,
|
|
105
|
+
fetch, // pass your server-side fetch
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## UI components
|
|
62
110
|
|
|
63
|
-
Use `@progus/connector-ui` for
|
|
111
|
+
Use `@progus/connector-ui` for UI components. It re-exports cross-sell helpers
|
|
112
|
+
from `@progus/connector`.
|
|
64
113
|
|
|
65
114
|
## Environment variables
|
|
66
115
|
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createProgusConnector: () => createProgusConnector,
|
|
24
|
+
fetchAppsCatalog: () => fetchAppsCatalog,
|
|
25
|
+
getCrossSellOffers: () => getCrossSellOffers,
|
|
26
|
+
getCrossSellOffersFromApi: () => getCrossSellOffersFromApi,
|
|
24
27
|
normalizePartnerId: () => normalizePartnerId,
|
|
25
28
|
signPayload: () => signPayload
|
|
26
29
|
});
|
|
@@ -61,7 +64,19 @@ function createProgusConnector(config) {
|
|
|
61
64
|
const enableIdempotency = config.enableIdempotency !== false;
|
|
62
65
|
const validSubscriptionPeriods = /* @__PURE__ */ new Set(["ANNUAL", "EVERY_30_DAYS"]);
|
|
63
66
|
if (!fetchImpl) {
|
|
64
|
-
|
|
67
|
+
const message = "Fetch implementation is required";
|
|
68
|
+
logger?.error?.(message);
|
|
69
|
+
const fail = async () => ({ success: false, message });
|
|
70
|
+
return {
|
|
71
|
+
track: async () => ({ success: false, message }),
|
|
72
|
+
trackInstall: fail,
|
|
73
|
+
trackUninstall: fail,
|
|
74
|
+
trackSubscriptionPurchased: fail,
|
|
75
|
+
trackSubscriptionUpdated: fail,
|
|
76
|
+
trackSubscriptionCancelled: fail,
|
|
77
|
+
assignPartnerId: fail,
|
|
78
|
+
checkPartnerId: async () => ({ success: false, message, status: 500 })
|
|
79
|
+
};
|
|
65
80
|
}
|
|
66
81
|
async function postEvent(eventName, payload) {
|
|
67
82
|
const { shopDomain, partnerId, data, externalId } = payload;
|
|
@@ -234,9 +249,111 @@ function createProgusConnector(config) {
|
|
|
234
249
|
checkPartnerId
|
|
235
250
|
};
|
|
236
251
|
}
|
|
252
|
+
|
|
253
|
+
// src/crossSell.ts
|
|
254
|
+
function getCrossSellOffers(options = {}) {
|
|
255
|
+
const appsCatalog = options.appsCatalog ?? [];
|
|
256
|
+
const installedKeys = new Set(
|
|
257
|
+
[options.appName, ...options.installedAppKeys ?? []].filter(
|
|
258
|
+
(key) => Boolean(key)
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
const locale = options.locale;
|
|
262
|
+
return appsCatalog.filter((app) => app.enabled !== false).filter((app) => locale ? !app.locales || app.locales.includes(locale) : true).filter((app) => !installedKeys.has(app.key)).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
263
|
+
}
|
|
264
|
+
var DEFAULT_APPS_CATALOG_URL = "https://appsdata.progus.com/recommendations";
|
|
265
|
+
var FALLBACK_APPS_CATALOG = [
|
|
266
|
+
{
|
|
267
|
+
key: "progus_ai_studio",
|
|
268
|
+
type: "app",
|
|
269
|
+
title: "Progus AI Studio",
|
|
270
|
+
company: "Progus",
|
|
271
|
+
companyUrl: "https://progus.com",
|
|
272
|
+
desc: "Generate on-brand AI product & variant images in bulk.",
|
|
273
|
+
url: "https://apps.shopify.com/progus-ai-studio",
|
|
274
|
+
icon: "https://cdn.shopify.com/app-store/listing_images/9428a41ed53c66cd7329a2583cd2f3d9/icon/COje1rnS4pEDEAE=.png",
|
|
275
|
+
priority: 100,
|
|
276
|
+
enabled: true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
key: "progus_trust_badges",
|
|
280
|
+
type: "app",
|
|
281
|
+
title: "Progus Trust Badges",
|
|
282
|
+
company: "Progus",
|
|
283
|
+
companyUrl: "https://progus.com",
|
|
284
|
+
desc: "Add Trust Badges to your store to build trust and credibility.",
|
|
285
|
+
url: "https://apps.shopify.com/progus-trust-badges-1",
|
|
286
|
+
icon: "https://cdn.shopify.com/app-store/listing_images/f9d0009e237f27d2db35b41ef99be858/icon/CJ3y1qDn1JEDEAE=.png",
|
|
287
|
+
priority: 80,
|
|
288
|
+
enabled: true
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
key: "progus_cod",
|
|
292
|
+
type: "app",
|
|
293
|
+
title: "Progus COD",
|
|
294
|
+
company: "Progus",
|
|
295
|
+
companyUrl: "https://progus.com",
|
|
296
|
+
desc: "Automate COD Fees & Hide/Show Cash on Delivery by Rules.",
|
|
297
|
+
url: "https://apps.shopify.com/progus-cod",
|
|
298
|
+
icon: "https://cdn.shopify.com/app-store/listing_images/bc537219cc3ed2bd4e7e3e683fe6b74a/icon/CMi_6dTEkIoDEAE=.png",
|
|
299
|
+
priority: 90,
|
|
300
|
+
enabled: true
|
|
301
|
+
}
|
|
302
|
+
];
|
|
303
|
+
function buildFallbackCatalog(options) {
|
|
304
|
+
const appName = options.appName;
|
|
305
|
+
const limit = typeof options.limit === "number" ? options.limit : 3;
|
|
306
|
+
let items = FALLBACK_APPS_CATALOG.filter((app) => app.enabled !== false);
|
|
307
|
+
if (appName) {
|
|
308
|
+
items = items.filter((app) => app.key !== appName);
|
|
309
|
+
}
|
|
310
|
+
if (limit > 0) {
|
|
311
|
+
items = items.slice(0, limit);
|
|
312
|
+
}
|
|
313
|
+
return items;
|
|
314
|
+
}
|
|
315
|
+
async function fetchAppsCatalog(options = {}) {
|
|
316
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
317
|
+
const logger = options.logger ?? console;
|
|
318
|
+
const appName = options.appName;
|
|
319
|
+
const baseUrl = stripTrailingSlash(options.appsCatalogUrl ?? DEFAULT_APPS_CATALOG_URL);
|
|
320
|
+
const limit = typeof options.limit === "number" ? options.limit : 3;
|
|
321
|
+
const params = new URLSearchParams();
|
|
322
|
+
if (appName) params.set("appName", appName);
|
|
323
|
+
if (limit > 0) params.set("limit", String(limit));
|
|
324
|
+
const url = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
|
325
|
+
if (!fetchImpl) {
|
|
326
|
+
logger?.error?.("Failed to fetch apps catalog", {
|
|
327
|
+
error: "Fetch implementation is required"
|
|
328
|
+
});
|
|
329
|
+
return buildFallbackCatalog(options);
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const response = await fetchImpl(url);
|
|
333
|
+
const text = await response.text();
|
|
334
|
+
const parsed = safeJsonParse(text);
|
|
335
|
+
if (!response.ok || !parsed) {
|
|
336
|
+
logger?.error?.("Failed to fetch apps catalog", { status: response.status });
|
|
337
|
+
return buildFallbackCatalog(options);
|
|
338
|
+
}
|
|
339
|
+
return parsed;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
logger?.error?.("Failed to fetch apps catalog", {
|
|
342
|
+
error: error instanceof Error ? error.message : String(error)
|
|
343
|
+
});
|
|
344
|
+
return buildFallbackCatalog(options);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function getCrossSellOffersFromApi(options = {}) {
|
|
348
|
+
const appsCatalog = options.appsCatalog ?? await fetchAppsCatalog(options);
|
|
349
|
+
return getCrossSellOffers({ ...options, appsCatalog });
|
|
350
|
+
}
|
|
237
351
|
// Annotate the CommonJS export names for ESM import in node:
|
|
238
352
|
0 && (module.exports = {
|
|
239
353
|
createProgusConnector,
|
|
354
|
+
fetchAppsCatalog,
|
|
355
|
+
getCrossSellOffers,
|
|
356
|
+
getCrossSellOffersFromApi,
|
|
240
357
|
normalizePartnerId,
|
|
241
358
|
signPayload
|
|
242
359
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -44,6 +44,32 @@ type SubscriptionEventData = {
|
|
|
44
44
|
subscriptionPrice?: number | string;
|
|
45
45
|
subscriptionPeriod?: string;
|
|
46
46
|
};
|
|
47
|
+
type AppsCatalogEntry = {
|
|
48
|
+
key: string;
|
|
49
|
+
type: string;
|
|
50
|
+
title: string;
|
|
51
|
+
company?: string;
|
|
52
|
+
companyUrl?: string;
|
|
53
|
+
desc?: string;
|
|
54
|
+
url: string;
|
|
55
|
+
icon?: string;
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
priority?: number;
|
|
58
|
+
locales?: string[];
|
|
59
|
+
};
|
|
60
|
+
type CrossSellOptions = {
|
|
61
|
+
appName?: string;
|
|
62
|
+
installedAppKeys?: string[];
|
|
63
|
+
locale?: string;
|
|
64
|
+
shopPlan?: string;
|
|
65
|
+
appsCatalog?: AppsCatalogEntry[];
|
|
66
|
+
};
|
|
67
|
+
type CrossSellFetchOptions = CrossSellOptions & {
|
|
68
|
+
appsCatalogUrl?: string;
|
|
69
|
+
limit?: number;
|
|
70
|
+
fetch?: FetchLike;
|
|
71
|
+
logger?: Logger;
|
|
72
|
+
};
|
|
47
73
|
|
|
48
74
|
type Connector = {
|
|
49
75
|
track: (eventName: TrackEventParams["eventName"], payload: Omit<TrackEventParams, "eventName">) => Promise<TrackResult>;
|
|
@@ -75,8 +101,12 @@ type Connector = {
|
|
|
75
101
|
};
|
|
76
102
|
declare function createProgusConnector(config: ConnectorConfig): Connector;
|
|
77
103
|
|
|
104
|
+
declare function getCrossSellOffers(options?: CrossSellOptions): AppsCatalogEntry[];
|
|
105
|
+
declare function fetchAppsCatalog(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
|
|
106
|
+
declare function getCrossSellOffersFromApi(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
|
|
107
|
+
|
|
78
108
|
declare function signPayload(body: string, secret: string): string;
|
|
79
109
|
|
|
80
110
|
declare function normalizePartnerId(value?: string | null): string | null;
|
|
81
111
|
|
|
82
|
-
export { type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type Logger, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, createProgusConnector, normalizePartnerId, signPayload };
|
|
112
|
+
export { type AppsCatalogEntry, type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type CrossSellFetchOptions, type CrossSellOptions, type Logger, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, createProgusConnector, fetchAppsCatalog, getCrossSellOffers, getCrossSellOffersFromApi, normalizePartnerId, signPayload };
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,32 @@ type SubscriptionEventData = {
|
|
|
44
44
|
subscriptionPrice?: number | string;
|
|
45
45
|
subscriptionPeriod?: string;
|
|
46
46
|
};
|
|
47
|
+
type AppsCatalogEntry = {
|
|
48
|
+
key: string;
|
|
49
|
+
type: string;
|
|
50
|
+
title: string;
|
|
51
|
+
company?: string;
|
|
52
|
+
companyUrl?: string;
|
|
53
|
+
desc?: string;
|
|
54
|
+
url: string;
|
|
55
|
+
icon?: string;
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
priority?: number;
|
|
58
|
+
locales?: string[];
|
|
59
|
+
};
|
|
60
|
+
type CrossSellOptions = {
|
|
61
|
+
appName?: string;
|
|
62
|
+
installedAppKeys?: string[];
|
|
63
|
+
locale?: string;
|
|
64
|
+
shopPlan?: string;
|
|
65
|
+
appsCatalog?: AppsCatalogEntry[];
|
|
66
|
+
};
|
|
67
|
+
type CrossSellFetchOptions = CrossSellOptions & {
|
|
68
|
+
appsCatalogUrl?: string;
|
|
69
|
+
limit?: number;
|
|
70
|
+
fetch?: FetchLike;
|
|
71
|
+
logger?: Logger;
|
|
72
|
+
};
|
|
47
73
|
|
|
48
74
|
type Connector = {
|
|
49
75
|
track: (eventName: TrackEventParams["eventName"], payload: Omit<TrackEventParams, "eventName">) => Promise<TrackResult>;
|
|
@@ -75,8 +101,12 @@ type Connector = {
|
|
|
75
101
|
};
|
|
76
102
|
declare function createProgusConnector(config: ConnectorConfig): Connector;
|
|
77
103
|
|
|
104
|
+
declare function getCrossSellOffers(options?: CrossSellOptions): AppsCatalogEntry[];
|
|
105
|
+
declare function fetchAppsCatalog(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
|
|
106
|
+
declare function getCrossSellOffersFromApi(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
|
|
107
|
+
|
|
78
108
|
declare function signPayload(body: string, secret: string): string;
|
|
79
109
|
|
|
80
110
|
declare function normalizePartnerId(value?: string | null): string | null;
|
|
81
111
|
|
|
82
|
-
export { type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type Logger, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, createProgusConnector, normalizePartnerId, signPayload };
|
|
112
|
+
export { type AppsCatalogEntry, type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type CrossSellFetchOptions, type CrossSellOptions, type Logger, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, createProgusConnector, fetchAppsCatalog, getCrossSellOffers, getCrossSellOffersFromApi, normalizePartnerId, signPayload };
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,19 @@ function createProgusConnector(config) {
|
|
|
33
33
|
const enableIdempotency = config.enableIdempotency !== false;
|
|
34
34
|
const validSubscriptionPeriods = /* @__PURE__ */ new Set(["ANNUAL", "EVERY_30_DAYS"]);
|
|
35
35
|
if (!fetchImpl) {
|
|
36
|
-
|
|
36
|
+
const message = "Fetch implementation is required";
|
|
37
|
+
logger?.error?.(message);
|
|
38
|
+
const fail = async () => ({ success: false, message });
|
|
39
|
+
return {
|
|
40
|
+
track: async () => ({ success: false, message }),
|
|
41
|
+
trackInstall: fail,
|
|
42
|
+
trackUninstall: fail,
|
|
43
|
+
trackSubscriptionPurchased: fail,
|
|
44
|
+
trackSubscriptionUpdated: fail,
|
|
45
|
+
trackSubscriptionCancelled: fail,
|
|
46
|
+
assignPartnerId: fail,
|
|
47
|
+
checkPartnerId: async () => ({ success: false, message, status: 500 })
|
|
48
|
+
};
|
|
37
49
|
}
|
|
38
50
|
async function postEvent(eventName, payload) {
|
|
39
51
|
const { shopDomain, partnerId, data, externalId } = payload;
|
|
@@ -206,8 +218,110 @@ function createProgusConnector(config) {
|
|
|
206
218
|
checkPartnerId
|
|
207
219
|
};
|
|
208
220
|
}
|
|
221
|
+
|
|
222
|
+
// src/crossSell.ts
|
|
223
|
+
function getCrossSellOffers(options = {}) {
|
|
224
|
+
const appsCatalog = options.appsCatalog ?? [];
|
|
225
|
+
const installedKeys = new Set(
|
|
226
|
+
[options.appName, ...options.installedAppKeys ?? []].filter(
|
|
227
|
+
(key) => Boolean(key)
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
const locale = options.locale;
|
|
231
|
+
return appsCatalog.filter((app) => app.enabled !== false).filter((app) => locale ? !app.locales || app.locales.includes(locale) : true).filter((app) => !installedKeys.has(app.key)).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
232
|
+
}
|
|
233
|
+
var DEFAULT_APPS_CATALOG_URL = "https://appsdata.progus.com/recommendations";
|
|
234
|
+
var FALLBACK_APPS_CATALOG = [
|
|
235
|
+
{
|
|
236
|
+
key: "progus_ai_studio",
|
|
237
|
+
type: "app",
|
|
238
|
+
title: "Progus AI Studio",
|
|
239
|
+
company: "Progus",
|
|
240
|
+
companyUrl: "https://progus.com",
|
|
241
|
+
desc: "Generate on-brand AI product & variant images in bulk.",
|
|
242
|
+
url: "https://apps.shopify.com/progus-ai-studio",
|
|
243
|
+
icon: "https://cdn.shopify.com/app-store/listing_images/9428a41ed53c66cd7329a2583cd2f3d9/icon/COje1rnS4pEDEAE=.png",
|
|
244
|
+
priority: 100,
|
|
245
|
+
enabled: true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
key: "progus_trust_badges",
|
|
249
|
+
type: "app",
|
|
250
|
+
title: "Progus Trust Badges",
|
|
251
|
+
company: "Progus",
|
|
252
|
+
companyUrl: "https://progus.com",
|
|
253
|
+
desc: "Add Trust Badges to your store to build trust and credibility.",
|
|
254
|
+
url: "https://apps.shopify.com/progus-trust-badges-1",
|
|
255
|
+
icon: "https://cdn.shopify.com/app-store/listing_images/f9d0009e237f27d2db35b41ef99be858/icon/CJ3y1qDn1JEDEAE=.png",
|
|
256
|
+
priority: 80,
|
|
257
|
+
enabled: true
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
key: "progus_cod",
|
|
261
|
+
type: "app",
|
|
262
|
+
title: "Progus COD",
|
|
263
|
+
company: "Progus",
|
|
264
|
+
companyUrl: "https://progus.com",
|
|
265
|
+
desc: "Automate COD Fees & Hide/Show Cash on Delivery by Rules.",
|
|
266
|
+
url: "https://apps.shopify.com/progus-cod",
|
|
267
|
+
icon: "https://cdn.shopify.com/app-store/listing_images/bc537219cc3ed2bd4e7e3e683fe6b74a/icon/CMi_6dTEkIoDEAE=.png",
|
|
268
|
+
priority: 90,
|
|
269
|
+
enabled: true
|
|
270
|
+
}
|
|
271
|
+
];
|
|
272
|
+
function buildFallbackCatalog(options) {
|
|
273
|
+
const appName = options.appName;
|
|
274
|
+
const limit = typeof options.limit === "number" ? options.limit : 3;
|
|
275
|
+
let items = FALLBACK_APPS_CATALOG.filter((app) => app.enabled !== false);
|
|
276
|
+
if (appName) {
|
|
277
|
+
items = items.filter((app) => app.key !== appName);
|
|
278
|
+
}
|
|
279
|
+
if (limit > 0) {
|
|
280
|
+
items = items.slice(0, limit);
|
|
281
|
+
}
|
|
282
|
+
return items;
|
|
283
|
+
}
|
|
284
|
+
async function fetchAppsCatalog(options = {}) {
|
|
285
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
286
|
+
const logger = options.logger ?? console;
|
|
287
|
+
const appName = options.appName;
|
|
288
|
+
const baseUrl = stripTrailingSlash(options.appsCatalogUrl ?? DEFAULT_APPS_CATALOG_URL);
|
|
289
|
+
const limit = typeof options.limit === "number" ? options.limit : 3;
|
|
290
|
+
const params = new URLSearchParams();
|
|
291
|
+
if (appName) params.set("appName", appName);
|
|
292
|
+
if (limit > 0) params.set("limit", String(limit));
|
|
293
|
+
const url = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
|
294
|
+
if (!fetchImpl) {
|
|
295
|
+
logger?.error?.("Failed to fetch apps catalog", {
|
|
296
|
+
error: "Fetch implementation is required"
|
|
297
|
+
});
|
|
298
|
+
return buildFallbackCatalog(options);
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const response = await fetchImpl(url);
|
|
302
|
+
const text = await response.text();
|
|
303
|
+
const parsed = safeJsonParse(text);
|
|
304
|
+
if (!response.ok || !parsed) {
|
|
305
|
+
logger?.error?.("Failed to fetch apps catalog", { status: response.status });
|
|
306
|
+
return buildFallbackCatalog(options);
|
|
307
|
+
}
|
|
308
|
+
return parsed;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
logger?.error?.("Failed to fetch apps catalog", {
|
|
311
|
+
error: error instanceof Error ? error.message : String(error)
|
|
312
|
+
});
|
|
313
|
+
return buildFallbackCatalog(options);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function getCrossSellOffersFromApi(options = {}) {
|
|
317
|
+
const appsCatalog = options.appsCatalog ?? await fetchAppsCatalog(options);
|
|
318
|
+
return getCrossSellOffers({ ...options, appsCatalog });
|
|
319
|
+
}
|
|
209
320
|
export {
|
|
210
321
|
createProgusConnector,
|
|
322
|
+
fetchAppsCatalog,
|
|
323
|
+
getCrossSellOffers,
|
|
324
|
+
getCrossSellOffersFromApi,
|
|
211
325
|
normalizePartnerId,
|
|
212
326
|
signPayload
|
|
213
327
|
};
|