@peektravel/app-utilities 0.9.0 → 0.9.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 CHANGED
@@ -317,9 +317,12 @@ event data**; the fields it shares with the token (`installId` / `accountId` /
317
317
  the body omits them. The token authenticates the whole delivery, so the body is
318
318
  trusted within a verified request. This webhook is the only source of
319
319
  `accountId`, `timezone`, and `apiUrl` in the package, since no GraphQL read
320
- returns them — **persist them per install**. `apiUrl` is the install's app
321
- endpoint: use it **as given** (don't reconstruct it from `appId`) — pass it as the
322
- access service's `apiUrl`, or hand the whole install to
320
+ returns them — **persist them per install**. `apiUrl` is the install's **base API
321
+ URL**: pass it straight through (don't reconstruct it from `appId`) as the access
322
+ service's `apiUrl` — each service class appends its own backoffice slug (e.g.
323
+ `peek_backoffice_api-v1`) when it makes a call, stripping the slug back off if the
324
+ URL you pass already carries it and throwing if it carries a different platform's
325
+ slug — or hand the whole install to
323
326
  `createAccessServiceForInstall(install, { jwtSecret, issuer })`, which picks the
324
327
  service by `platform` and wires the endpoint for you. Every event is a **full
325
328
  snapshot** — an `update_installed` carries the same fields as the original
package/dist/index.cjs CHANGED
@@ -23,9 +23,29 @@ function _interopNamespace(e) {
23
23
 
24
24
  var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
25
25
 
26
+ // src/internal/api-endpoints.ts
27
+ var ApiEndpoints = class {
28
+ constructor(paths) {
29
+ this.paths = paths;
30
+ }
31
+ paths;
32
+ /** The relative path (slug) for `endpoint`, appended to the base API URL. */
33
+ pathFor(endpoint) {
34
+ return this.paths[endpoint];
35
+ }
36
+ };
37
+ function joinUrl(...parts) {
38
+ return parts.filter((part) => part.length > 0).join("/");
39
+ }
40
+
26
41
  // src/internal/peek/gateway-endpoints.ts
27
42
  var SALES_ENDPOINT = "sales";
28
43
  var V2_EXTENDABLE_SLUG = "peek_backoffice_api-v1";
44
+ function peekApiEndpoints(useRegistrySlug) {
45
+ return new ApiEndpoints({
46
+ [SALES_ENDPOINT]: useRegistrySlug ? V2_EXTENDABLE_SLUG : SALES_ENDPOINT
47
+ });
48
+ }
29
49
 
30
50
  // src/internal/peek/account-users/account-user-converter.ts
31
51
  var ACTIVE_STATUS = "ACTIVE";
@@ -2099,6 +2119,21 @@ function requireNonEmpty(value, name, serviceName) {
2099
2119
  throw new Error(`${serviceName}: "${name}" is required`);
2100
2120
  }
2101
2121
  }
2122
+ var EXTENDABLE_SLUG_RE = /^([a-z0-9]+)[-_]backoffice[-_]api[-_]v1$/i;
2123
+ function resolveBaseApiUrl(apiUrl, platformSlug, serviceName) {
2124
+ const trimmed = apiUrl.replace(/\/+$/, "");
2125
+ const lastSegment = trimmed.slice(trimmed.lastIndexOf("/") + 1);
2126
+ const found = EXTENDABLE_SLUG_RE.exec(lastSegment);
2127
+ if (!found) return trimmed;
2128
+ const foundPlatform = found[1].toLowerCase();
2129
+ const expectedPlatform = EXTENDABLE_SLUG_RE.exec(platformSlug)[1].toLowerCase();
2130
+ if (foundPlatform !== expectedPlatform) {
2131
+ throw new Error(
2132
+ `${serviceName}: "apiUrl" carries a "${foundPlatform}" backoffice slug but this service targets "${expectedPlatform}" \u2014 pass the install's ${expectedPlatform} app endpoint`
2133
+ );
2134
+ }
2135
+ return trimmed.slice(0, trimmed.lastIndexOf("/"));
2136
+ }
2102
2137
  function createTokenManager(config) {
2103
2138
  return new TokenManager({
2104
2139
  secret: config.jwtSecret,
@@ -2149,7 +2184,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
2149
2184
  // src/internal/sdk-headers.ts
2150
2185
  var SDK_HEADER_NAME = "x-peek-sdk";
2151
2186
  var SDK_PLATFORM = "js";
2152
- var SDK_VERSION = "0.9.0";
2187
+ var SDK_VERSION = "0.9.1";
2153
2188
  var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
2154
2189
 
2155
2190
  // src/internal/peek/graphql-client.ts
@@ -2197,10 +2232,8 @@ var GraphQLClient = class {
2197
2232
  );
2198
2233
  }
2199
2234
  endpoint(endpointName) {
2200
- const { apiUrl, baseUrl, appId, endpointPathPrefix } = this.options;
2201
- if (apiUrl) return apiUrl;
2202
- const prefix = endpointPathPrefix ? `${endpointPathPrefix}/` : "";
2203
- return `${baseUrl}/${appId}/${prefix}${endpointName}`;
2235
+ const { baseApiUrl, endpoints } = this.options;
2236
+ return joinUrl(baseApiUrl, endpoints.pathFor(endpointName));
2204
2237
  }
2205
2238
  buildHeaders() {
2206
2239
  const headers = {
@@ -3823,17 +3856,17 @@ var PeekAccessService = class {
3823
3856
  this.jwtSecret = config.jwtSecret;
3824
3857
  const logger = config.logger ?? noopLogger;
3825
3858
  const tokens = createTokenManager(config);
3859
+ const useRegistrySlug = hasApiUrl || isV2;
3826
3860
  const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
3861
+ const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, V2_EXTENDABLE_SLUG, "PeekAccessService") : `${config.baseUrl ?? defaultBaseUrl}/${config.appId}`;
3827
3862
  this.client = new GraphQLClient({
3828
- apiUrl: config.apiUrl,
3829
- baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? defaultBaseUrl,
3830
- appId: hasApiUrl ? void 0 : config.appId,
3863
+ baseApiUrl,
3864
+ endpoints: peekApiEndpoints(useRegistrySlug),
3831
3865
  gatewayKey: config.gatewayKey,
3832
3866
  getToken: () => tokens.getToken(),
3833
3867
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
3834
3868
  logger,
3835
- fetchFn: config.fetch ?? globalThis.fetch,
3836
- endpointPathPrefix: !hasApiUrl && isV2 ? V2_EXTENDABLE_SLUG : void 0
3869
+ fetchFn: config.fetch ?? globalThis.fetch
3837
3870
  });
3838
3871
  this.productServiceOptions = {
3839
3872
  itemOptionsPageSize: config.itemOptionsPageSize
@@ -4187,7 +4220,11 @@ var PeekAccessService = class {
4187
4220
 
4188
4221
  // src/internal/cng/endpoints.ts
4189
4222
  var CNG_EXTENDABLE_SLUG = "cng_backoffice_api-v1";
4223
+ var CNG_PRODUCTS_ENDPOINT = "products";
4190
4224
  var PRODUCTS_PATH = "api/v2/app-registry/products?active=1";
4225
+ var CNG_API_ENDPOINTS = new ApiEndpoints({
4226
+ [CNG_PRODUCTS_ENDPOINT]: CNG_EXTENDABLE_SLUG
4227
+ });
4191
4228
 
4192
4229
  // src/models/cng/product.ts
4193
4230
  var ACTIVITY_PRODUCT_TYPE2 = "ACTIVITY";
@@ -4221,7 +4258,10 @@ var CngProductService = class {
4221
4258
  * ```
4222
4259
  */
4223
4260
  async getAllActivities() {
4224
- const body = await this.client.get(PRODUCTS_PATH);
4261
+ const body = await this.client.get(
4262
+ CNG_PRODUCTS_ENDPOINT,
4263
+ PRODUCTS_PATH
4264
+ );
4225
4265
  return fromProductNodes(body?.data ?? []);
4226
4266
  }
4227
4267
  };
@@ -4243,9 +4283,9 @@ var RestClient = class {
4243
4283
  * @throws {CngPermissionError} on HTTP 403 (app missing a permission)
4244
4284
  * @throws {CngApiError} on any other non-2xx response
4245
4285
  */
4246
- async get(path) {
4286
+ async get(endpoint, path) {
4247
4287
  const { logger } = this.options;
4248
- const url = this.endpoint(path);
4288
+ const url = this.endpoint(endpoint, path);
4249
4289
  logger.info("Making CNG request", { url, path });
4250
4290
  return requestWithRetry(
4251
4291
  this.options,
@@ -4267,10 +4307,9 @@ var RestClient = class {
4267
4307
  }
4268
4308
  );
4269
4309
  }
4270
- endpoint(path) {
4271
- const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
4272
- if (apiUrl) return `${apiUrl}/${path}`;
4273
- return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
4310
+ endpoint(endpoint, path) {
4311
+ const { baseApiUrl, endpoints } = this.options;
4312
+ return joinUrl(baseApiUrl, endpoints.pathFor(endpoint), path);
4274
4313
  }
4275
4314
  buildHeaders() {
4276
4315
  return {
@@ -4300,11 +4339,10 @@ var CngAccessService = class {
4300
4339
  if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "CngAccessService");
4301
4340
  const logger = config.logger ?? noopLogger;
4302
4341
  const tokens = createTokenManager(config);
4342
+ const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, CNG_EXTENDABLE_SLUG, "CngAccessService") : `${config.baseUrl ?? DEFAULT_BASE_URL2}/${config.appId}`;
4303
4343
  this.client = new RestClient({
4304
- apiUrl: config.apiUrl,
4305
- baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL2,
4306
- appId: hasApiUrl ? void 0 : config.appId,
4307
- extendableSlug: CNG_EXTENDABLE_SLUG,
4344
+ baseApiUrl,
4345
+ endpoints: CNG_API_ENDPOINTS,
4308
4346
  getToken: () => tokens.getToken(),
4309
4347
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
4310
4348
  logger,
@@ -4329,7 +4367,11 @@ var CngAccessService = class {
4329
4367
 
4330
4368
  // src/internal/acme/endpoints.ts
4331
4369
  var ACME_EXTENDABLE_SLUG = "acme_backoffice_api-v1";
4370
+ var ACME_TEMPLATES_ENDPOINT = "templates";
4332
4371
  var TEMPLATES_PATH = "v2/b2b/event/templates/names?pageSize=-1&page=1";
4372
+ var ACME_API_ENDPOINTS = new ApiEndpoints({
4373
+ [ACME_TEMPLATES_ENDPOINT]: ACME_EXTENDABLE_SLUG
4374
+ });
4333
4375
 
4334
4376
  // src/models/acme/product.ts
4335
4377
  var ACME_ACTIVITY_TYPE = "standard";
@@ -4367,6 +4409,7 @@ var AcmeProductService = class {
4367
4409
  */
4368
4410
  async getAllActivities() {
4369
4411
  const body = await this.client.get(
4412
+ ACME_TEMPLATES_ENDPOINT,
4370
4413
  TEMPLATES_PATH
4371
4414
  );
4372
4415
  const nodes = Array.isArray(body) ? body : body?.list ?? [];
@@ -4389,9 +4432,9 @@ var RestClient2 = class {
4389
4432
  * @throws {RateLimitError} on HTTP 429 after retries are exhausted
4390
4433
  * @throws {AcmeApiError} on any other non-2xx response
4391
4434
  */
4392
- async get(path) {
4435
+ async get(endpoint, path) {
4393
4436
  const { logger } = this.options;
4394
- const url = this.endpoint(path);
4437
+ const url = this.endpoint(endpoint, path);
4395
4438
  logger.info("Making ACME request", { url, path });
4396
4439
  return requestWithRetry(
4397
4440
  this.options,
@@ -4408,10 +4451,9 @@ var RestClient2 = class {
4408
4451
  }
4409
4452
  );
4410
4453
  }
4411
- endpoint(path) {
4412
- const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
4413
- if (apiUrl) return `${apiUrl}/${path}`;
4414
- return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
4454
+ endpoint(endpoint, path) {
4455
+ const { baseApiUrl, endpoints } = this.options;
4456
+ return joinUrl(baseApiUrl, endpoints.pathFor(endpoint), path);
4415
4457
  }
4416
4458
  buildHeaders() {
4417
4459
  return {
@@ -4435,11 +4477,10 @@ var AcmeAccessService = class {
4435
4477
  if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "AcmeAccessService");
4436
4478
  const logger = config.logger ?? noopLogger;
4437
4479
  const tokens = createTokenManager(config);
4480
+ const baseApiUrl = hasApiUrl ? resolveBaseApiUrl(config.apiUrl, ACME_EXTENDABLE_SLUG, "AcmeAccessService") : `${config.baseUrl ?? DEFAULT_BASE_URL3}/${config.appId}`;
4438
4481
  this.client = new RestClient2({
4439
- apiUrl: config.apiUrl,
4440
- baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL3,
4441
- appId: hasApiUrl ? void 0 : config.appId,
4442
- extendableSlug: ACME_EXTENDABLE_SLUG,
4482
+ baseApiUrl,
4483
+ endpoints: ACME_API_ENDPOINTS,
4443
4484
  getToken: () => tokens.getToken(),
4444
4485
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
4445
4486
  logger,