@amityco/ts-sdk-react-native 7.28.0 → 7.29.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.
Files changed (32) hide show
  1. package/dist/@types/core/errors.d.ts +2 -1
  2. package/dist/@types/core/errors.d.ts.map +1 -1
  3. package/dist/@types/core/transport.d.ts +1 -0
  4. package/dist/@types/core/transport.d.ts.map +1 -1
  5. package/dist/@types/domains/client.d.ts +86 -0
  6. package/dist/@types/domains/client.d.ts.map +1 -1
  7. package/dist/client/api/createClient.d.ts.map +1 -1
  8. package/dist/client/api/getModuleSettings.d.ts +25 -0
  9. package/dist/client/api/getModuleSettings.d.ts.map +1 -0
  10. package/dist/client/api/index.d.ts +1 -0
  11. package/dist/client/api/index.d.ts.map +1 -1
  12. package/dist/client/api/tests/integration/moduleSettings.integration.test.d.ts +2 -0
  13. package/dist/client/api/tests/integration/moduleSettings.integration.test.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +1 -0
  15. package/dist/client/index.d.ts.map +1 -1
  16. package/dist/client/utils/parseModuleSettings.d.ts +9 -0
  17. package/dist/client/utils/parseModuleSettings.d.ts.map +1 -0
  18. package/dist/client/utils/resolveModuleAvailability.d.ts +39 -0
  19. package/dist/client/utils/resolveModuleAvailability.d.ts.map +1 -0
  20. package/dist/client/utils/tests/resolveModuleAvailability.test.d.ts +2 -0
  21. package/dist/client/utils/tests/resolveModuleAvailability.test.d.ts.map +1 -0
  22. package/dist/core/errors.d.ts +20 -0
  23. package/dist/core/errors.d.ts.map +1 -1
  24. package/dist/core/transports/tests/moduleNotEntitled.test.d.ts +2 -0
  25. package/dist/core/transports/tests/moduleNotEntitled.test.d.ts.map +1 -0
  26. package/dist/core/transports/utils.d.ts.map +1 -1
  27. package/dist/index.cjs.js +230 -3
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.esm.js +229 -4
  31. package/dist/index.umd.js +1 -1
  32. package/package.json +1 -1
package/dist/index.cjs.js CHANGED
@@ -353,8 +353,8 @@ exports.AmityLiveViewerCountMode = void 0;
353
353
 
354
354
  function getVersion() {
355
355
  try {
356
- // the string ''v7.28.0-cjs'' should be replaced by actual value by @rollup/plugin-replace
357
- return 'v7.28.0-cjs';
356
+ // the string ''v7.29.0-cjs'' should be replaced by actual value by @rollup/plugin-replace
357
+ return 'v7.29.0-cjs';
358
358
  }
359
359
  catch (error) {
360
360
  return '__dev__';
@@ -833,6 +833,28 @@ class ASCConnectionError extends ASCError {
833
833
  this.event = event;
834
834
  }
835
835
  }
836
+ /**
837
+ * Thrown when the network does not own a module the call needs. Raised from
838
+ * any module endpoint under `enforce` enforcement, mapped from app code
839
+ * `400325` — never from HTTP 403 alone, which already covers a role problem
840
+ * (`400300` / `400301`), a visitor (`403999`) and a bot (`403998`).
841
+ *
842
+ * `modules` carries **every** key the refusal named, as raw strings, so a
843
+ * caller can say which entitlement is absent without a second request.
844
+ *
845
+ * @category Errors
846
+ */
847
+ class AmityModuleNotEntitledError extends ASCApiError {
848
+ /**
849
+ * @param message The server's message, e.g. `Module not entitled: chat, live`
850
+ * @param modules Every module key from the rejection's `data.modules`
851
+ */
852
+ constructor(message, modules = []) {
853
+ super(message, 400325 /* Amity.ServerError.MODULE_NOT_ENTITLED */, "error" /* Amity.ErrorLevel.ERROR */);
854
+ this.modules = modules;
855
+ this.type = 'AmityModuleNotEntitledError';
856
+ }
857
+ }
836
858
  /**
837
859
  * Input sanitization related error
838
860
  * @category Errors
@@ -1994,6 +2016,22 @@ function computeUrl(type, region) {
1994
2016
 
1995
2017
  const uuid = () => uuid__default["default"].v4().toString();
1996
2018
 
2019
+ /**
2020
+ * Reads the module keys a `MODULE_NOT_ENTITLED` rejection named.
2021
+ *
2022
+ * Every entry is kept, as a raw string: one call can need more than one
2023
+ * module, and a key this build predates is still the only part of the
2024
+ * payload a customer can act on.
2025
+ *
2026
+ * @category Transport
2027
+ * @hidden
2028
+ */
2029
+ const extractNotEntitledModules = (data) => {
2030
+ const { modules } = data !== null && data !== void 0 ? data : {};
2031
+ if (!Array.isArray(modules))
2032
+ return [];
2033
+ return modules.filter((module) => typeof module === 'string');
2034
+ };
1997
2035
  /**
1998
2036
  * Unwraps v3 server payload (for error handling and type extraction)
1999
2037
  *
@@ -2013,6 +2051,15 @@ const unwrapPayload = (response) => {
2013
2051
  throw new ASCUnknownError(response.code);
2014
2052
  }
2015
2053
  else if (response.status === 'error') {
2054
+ /*
2055
+ * An entitlement refusal must not arrive as a generic failure: a customer
2056
+ * cannot then tell a module they have not bought from an outage. Keyed on
2057
+ * the app code, never on the HTTP status, so a role or membership problem
2058
+ * still maps to the generic error.
2059
+ */
2060
+ if (response.code === 400325 /* Amity.ServerError.MODULE_NOT_ENTITLED */) {
2061
+ throw new AmityModuleNotEntitledError(response.message, extractNotEntitledModules(response.data));
2062
+ }
2016
2063
  throw new ASCApiError(response.message, response.code, "error" /* Amity.ErrorLevel.ERROR */);
2017
2064
  }
2018
2065
  // unjust, but helps force uncast "T | undefined" to allow deconstruction without warnings
@@ -5546,6 +5593,95 @@ const getForYouFeedSetting = async () => {
5546
5593
  };
5547
5594
  /* end_public_function */
5548
5595
 
5596
+ const ENFORCEMENT_MODES = ['off', 'shadow', 'enforce'];
5597
+ /**
5598
+ * An unrecognised mode decodes to `off`, and so does an absent one. A mode
5599
+ * this build does not know must not withhold anything — defaulting to
5600
+ * `enforce` would let a server-side rename black out a customer's app.
5601
+ *
5602
+ * @category private
5603
+ */
5604
+ const parseEnforcement = (mode) => {
5605
+ const match = ENFORCEMENT_MODES.find(value => value === mode);
5606
+ return match !== null && match !== void 0 ? match : 'off';
5607
+ };
5608
+ const parseGrants = (modules) => {
5609
+ const grants = {};
5610
+ Object.entries(modules !== null && modules !== void 0 ? modules : {}).forEach(([key, granted]) => {
5611
+ grants[key] = granted === true;
5612
+ });
5613
+ return grants;
5614
+ };
5615
+ /**
5616
+ * Every catalog entry survives, including one whose key this build has never
5617
+ * heard of, so a module added server-side is still listable.
5618
+ *
5619
+ * @category private
5620
+ */
5621
+ const parseCatalog = (catalog) => {
5622
+ const parsed = {};
5623
+ Object.entries(catalog !== null && catalog !== void 0 ? catalog : {}).forEach(([key, definition]) => {
5624
+ const rawRequires = definition === null || definition === void 0 ? void 0 : definition.requires;
5625
+ const requires = Array.isArray(rawRequires)
5626
+ ? rawRequires.filter((entry) => typeof entry === 'string')
5627
+ : [];
5628
+ parsed[key] = {
5629
+ label: typeof (definition === null || definition === void 0 ? void 0 : definition.label) === 'string' ? definition.label : key,
5630
+ kind: (definition === null || definition === void 0 ? void 0 : definition.kind) === 'setting' ? 'setting' : 'api',
5631
+ requires,
5632
+ };
5633
+ });
5634
+ return parsed;
5635
+ };
5636
+ /**
5637
+ * Decodes `GET /api/v3/network-settings/modules`. Nothing is trimmed: the
5638
+ * catalog is the dependency table callers resolve against, and a client that
5639
+ * cannot see it has to hardcode one.
5640
+ *
5641
+ * @category private
5642
+ */
5643
+ const parseModuleSettings = (raw) => {
5644
+ var _a;
5645
+ return ({
5646
+ enforcement: parseEnforcement((_a = raw === null || raw === void 0 ? void 0 : raw.enforcement) !== null && _a !== void 0 ? _a : raw === null || raw === void 0 ? void 0 : raw.mode),
5647
+ modules: parseGrants(raw === null || raw === void 0 ? void 0 : raw.modules),
5648
+ catalog: parseCatalog(raw === null || raw === void 0 ? void 0 : raw.catalog),
5649
+ });
5650
+ };
5651
+
5652
+ /* begin_public_function
5653
+ id: client.getModuleSettings
5654
+ */
5655
+ /**
5656
+ * ```js
5657
+ * import { Client } from '@amityco/ts-sdk'
5658
+ * const moduleSettings = await Client.getModuleSettings()
5659
+ * ```
5660
+ *
5661
+ * Fetches the network's module entitlements: which modules the network was
5662
+ * granted, whether core is enforcing those grants, and the catalog of all
5663
+ * modules with their dependency chains.
5664
+ *
5665
+ * The payload is returned whole — `catalog` is the dependency table
5666
+ * {@link resolveModuleAvailability} reads, and a client that cannot see it has
5667
+ * to hardcode one.
5668
+ *
5669
+ * The read is **advisory**. It tells a host what to offer; under `enforce` the
5670
+ * backend refuses a call to an unowned module regardless of what was resolved
5671
+ * here, and that refusal arrives as `AmityModuleNotEntitledError`.
5672
+ *
5673
+ * @returns A Promise of {@link Amity.ModuleSettings} object
5674
+ *
5675
+ * @category Client API
5676
+ * @async
5677
+ */
5678
+ const getModuleSettings = async () => {
5679
+ const client = getActiveClient();
5680
+ const { data } = await client.http.get(`/api/v3/network-settings/modules`);
5681
+ return parseModuleSettings(data);
5682
+ };
5683
+ /* end_public_function */
5684
+
5549
5685
  const checkUserPermission = (userId, permission) => {
5550
5686
  var _a;
5551
5687
  const user = (_a = pullFromCache(['user', 'get', userId])) === null || _a === void 0 ? void 0 : _a.data;
@@ -9321,6 +9457,7 @@ const createClient = (apiKey, apiRegion = API_REGIONS.SG, { debugSession = DEFAU
9321
9457
  getMarkerSyncConsistentMode,
9322
9458
  getProductCatalogueSetting,
9323
9459
  getForYouFeedSetting,
9460
+ getModuleSettings,
9324
9461
  /**
9325
9462
  * Prefix for the deviceId key in the local storage or async storage.
9326
9463
  * This is allow user to have multiple SDK client and Mqtt client within the same app.
@@ -10410,6 +10547,92 @@ const getUserUnread = (callback) => {
10410
10547
  };
10411
10548
  };
10412
10549
 
10550
+ /**
10551
+ * ```js
10552
+ * import { Client, resolveModuleAvailability } from '@amityco/ts-sdk'
10553
+ *
10554
+ * const moduleSettings = await Client.getModuleSettings()
10555
+ *
10556
+ * if (resolveModuleAvailability(moduleSettings, 'live').isAvailable) {
10557
+ * // offer the Live entry point
10558
+ * }
10559
+ * ```
10560
+ *
10561
+ * Resolves whether one module is usable on this network.
10562
+ *
10563
+ * Availability is a function of all three fields of
10564
+ * {@link Amity.ModuleSettings} — the enforcement mode, the module's own grant,
10565
+ * and its prerequisite chain — combined in that order. Reading `modules`
10566
+ * directly contradicts the mode: staging answers `mode: 'off'` with
10567
+ * `modules: {}`, and every module still works.
10568
+ *
10569
+ * The dependency table comes from the payload's `catalog`, never from a
10570
+ * hardcoded copy, so a chain the backend changes takes effect without an SDK
10571
+ * release.
10572
+ *
10573
+ * The SDK does not hold the settings — the caller keeps what it fetched and
10574
+ * resolves against it. With no settings at all (a fetch that never succeeded)
10575
+ * the caller reads that as every module available: under `enforce` the backend
10576
+ * refuses the calls anyway, so the exposure is a screen that offers too much,
10577
+ * not one that withholds what a customer paid for.
10578
+ *
10579
+ * @param moduleSettings The network's module settings
10580
+ * @param moduleKey The module to resolve — an {@link Amity.ModuleKey}, so the
10581
+ * 15 keys this build knows complete and type-check while a catalog entry it
10582
+ * does not know still resolves.
10583
+ * @returns An {@link Amity.ModuleAvailability} answer
10584
+ *
10585
+ * @category Client API
10586
+ */
10587
+ const resolveModuleAvailability = (moduleSettings, moduleKey, seen = new Set()) => {
10588
+ var _a, _b, _c, _d, _e;
10589
+ const isGranted = (_a = moduleSettings.modules[moduleKey]) !== null && _a !== void 0 ? _a : false;
10590
+ /*
10591
+ * Not enforcing: nothing is withheld, and no graph walk is needed. This has
10592
+ * to come first — `off` and `shadow` resolve available whatever the grant
10593
+ * says, and a cycle would otherwise return unavailable under `off` too.
10594
+ */
10595
+ if (moduleSettings.enforcement !== 'enforce') {
10596
+ return { moduleKey, isAvailable: true, isGranted };
10597
+ }
10598
+ // A cycle in `requires` resolves unavailable rather than recursing.
10599
+ if (seen.has(moduleKey)) {
10600
+ return {
10601
+ moduleKey,
10602
+ isAvailable: false,
10603
+ isGranted,
10604
+ reason: 'prerequisiteUnavailable',
10605
+ unsatisfied: (_c = (_b = moduleSettings.catalog[moduleKey]) === null || _b === void 0 ? void 0 : _b.requires) !== null && _c !== void 0 ? _c : [],
10606
+ };
10607
+ }
10608
+ // The module's own grant is evaluated before its prerequisites, so
10609
+ // `notGranted` is reported in preference when both hold.
10610
+ if (!isGranted) {
10611
+ return { moduleKey, isAvailable: false, isGranted, reason: 'notGranted' };
10612
+ }
10613
+ const requires = (_e = (_d = moduleSettings.catalog[moduleKey]) === null || _d === void 0 ? void 0 : _d.requires) !== null && _e !== void 0 ? _e : [];
10614
+ if (requires.length === 0)
10615
+ return { moduleKey, isAvailable: true, isGranted };
10616
+ // `requires` is satisfied by any one entry, transitively.
10617
+ const isSatisfied = requires.some(dependency => resolveModuleAvailability(moduleSettings, dependency, new Set([...seen, moduleKey]))
10618
+ .isAvailable);
10619
+ if (isSatisfied)
10620
+ return { moduleKey, isAvailable: true, isGranted };
10621
+ /*
10622
+ * `unsatisfied` carries the whole `requires` list rather than one entry. Any
10623
+ * one of them would have satisfied the requirement, so naming a single
10624
+ * module misdirects: telling a Community-only customer "Comment needs post"
10625
+ * hides that Story would also have done.
10626
+ */
10627
+ return {
10628
+ moduleKey,
10629
+ isAvailable: false,
10630
+ isGranted,
10631
+ reason: 'prerequisiteUnavailable',
10632
+ unsatisfied: requires,
10633
+ };
10634
+ };
10635
+
10413
10636
  var index$t = /*#__PURE__*/Object.freeze({
10414
10637
  __proto__: null,
10415
10638
  getActiveClient: getActiveClient,
@@ -10436,6 +10659,7 @@ var index$t = /*#__PURE__*/Object.freeze({
10436
10659
  getShareableLinkConfiguration: getShareableLinkConfiguration,
10437
10660
  getProductCatalogueSetting: getProductCatalogueSetting,
10438
10661
  getForYouFeedSetting: getForYouFeedSetting,
10662
+ getModuleSettings: getModuleSettings,
10439
10663
  loginAsVisitor: loginAsVisitor,
10440
10664
  loginAsBot: loginAsBot,
10441
10665
  getCurrentUser: getCurrentUser,
@@ -10463,7 +10687,8 @@ var index$t = /*#__PURE__*/Object.freeze({
10463
10687
  startMarkerSync: startMarkerSync,
10464
10688
  startUnreadSync: startUnreadSync,
10465
10689
  stopUnreadSync: stopUnreadSync,
10466
- getMarkerSyncConsistentMode: getMarkerSyncConsistentMode
10690
+ getMarkerSyncConsistentMode: getMarkerSyncConsistentMode,
10691
+ resolveModuleAvailability: resolveModuleAvailability
10467
10692
  });
10468
10693
 
10469
10694
  /* begin_public_function
@@ -37074,6 +37299,7 @@ class AmityAttachmentProductTags {
37074
37299
  exports.API_REGIONS = API_REGIONS;
37075
37300
  exports.AdRepository = index$6;
37076
37301
  exports.AmityAttachmentProductTags = AmityAttachmentProductTags;
37302
+ exports.AmityModuleNotEntitledError = AmityModuleNotEntitledError;
37077
37303
  exports.CategoryRepository = index$g;
37078
37304
  exports.ChannelRepository = index$k;
37079
37305
  exports.Client = index$t;
@@ -37182,6 +37408,7 @@ exports.pushToCache = pushToCache;
37182
37408
  exports.queryCache = queryCache;
37183
37409
  exports.queryOptions = queryOptions;
37184
37410
  exports.queryRoles = queryRoles;
37411
+ exports.resolveModuleAvailability = resolveModuleAvailability;
37185
37412
  exports.restoreCache = restoreCache;
37186
37413
  exports.runQuery = runQuery;
37187
37414
  exports.sortByChannelSegment = sortByChannelSegment;
package/dist/index.d.ts CHANGED
@@ -35,4 +35,6 @@ export * as EventRepository from './eventRepository';
35
35
  export * as ProductRepository from './productRepository';
36
36
  export * as CuratedContentRepository from './curatedContentRepository';
37
37
  export { AmityAttachmentProductTags } from './productRepository/helpers';
38
+ export { AmityModuleNotEntitledError } from './core/errors';
39
+ export { resolveModuleAvailability } from './client/utils/resolveModuleAvailability';
38
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAC;AAEzB,cAAc,WAAW,CAAC;AAG1B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAE5B,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AAEnD,cAAc,YAAY,CAAC;AAG3B,OAAO,KAAK,kBAAkB,MAAM,sBAAsB,CAAC;AAE3D,cAAc,cAAc,CAAC;AAG7B,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,oBAAoB,MAAM,wBAAwB,CAAC;AAE/D,cAAc,iBAAiB,CAAC;AAGhC,OAAO,KAAK,mBAAmB,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,kBAAkB,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,sBAAsB,MAAM,0BAA0B,CAAC;AAEnE,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AAGrD,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAG/C,cAAc,gBAAgB,CAAC;AAE/B,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,oBAAoB,MAAM,wBAAwB,CAAC;AAE/D,OAAO,KAAK,sBAAsB,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAE3E,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,wBAAwB,MAAM,4BAA4B,CAAC;AAEvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAC;AAEzB,cAAc,WAAW,CAAC;AAG1B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAE5B,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AAEnD,cAAc,YAAY,CAAC;AAG3B,OAAO,KAAK,kBAAkB,MAAM,sBAAsB,CAAC;AAE3D,cAAc,cAAc,CAAC;AAG7B,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,oBAAoB,MAAM,wBAAwB,CAAC;AAE/D,cAAc,iBAAiB,CAAC;AAGhC,OAAO,KAAK,mBAAmB,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,kBAAkB,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,sBAAsB,MAAM,0BAA0B,CAAC;AAEnE,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AAGrD,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAG/C,cAAc,gBAAgB,CAAC;AAE/B,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,oBAAoB,MAAM,wBAAwB,CAAC;AAE/D,OAAO,KAAK,sBAAsB,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAE3E,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,wBAAwB,MAAM,4BAA4B,CAAC;AAEvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAEzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC"}
package/dist/index.esm.js CHANGED
@@ -318,8 +318,8 @@ var AmityLiveViewerCountMode;
318
318
 
319
319
  function getVersion() {
320
320
  try {
321
- // the string ''v7.28.0-esm'' should be replaced by actual value by @rollup/plugin-replace
322
- return 'v7.28.0-esm';
321
+ // the string ''v7.29.0-esm'' should be replaced by actual value by @rollup/plugin-replace
322
+ return 'v7.29.0-esm';
323
323
  }
324
324
  catch (error) {
325
325
  return '__dev__';
@@ -798,6 +798,28 @@ class ASCConnectionError extends ASCError {
798
798
  this.event = event;
799
799
  }
800
800
  }
801
+ /**
802
+ * Thrown when the network does not own a module the call needs. Raised from
803
+ * any module endpoint under `enforce` enforcement, mapped from app code
804
+ * `400325` — never from HTTP 403 alone, which already covers a role problem
805
+ * (`400300` / `400301`), a visitor (`403999`) and a bot (`403998`).
806
+ *
807
+ * `modules` carries **every** key the refusal named, as raw strings, so a
808
+ * caller can say which entitlement is absent without a second request.
809
+ *
810
+ * @category Errors
811
+ */
812
+ class AmityModuleNotEntitledError extends ASCApiError {
813
+ /**
814
+ * @param message The server's message, e.g. `Module not entitled: chat, live`
815
+ * @param modules Every module key from the rejection's `data.modules`
816
+ */
817
+ constructor(message, modules = []) {
818
+ super(message, 400325 /* Amity.ServerError.MODULE_NOT_ENTITLED */, "error" /* Amity.ErrorLevel.ERROR */);
819
+ this.modules = modules;
820
+ this.type = 'AmityModuleNotEntitledError';
821
+ }
822
+ }
801
823
  /**
802
824
  * Input sanitization related error
803
825
  * @category Errors
@@ -1959,6 +1981,22 @@ function computeUrl(type, region) {
1959
1981
 
1960
1982
  const uuid = () => uuid$1.v4().toString();
1961
1983
 
1984
+ /**
1985
+ * Reads the module keys a `MODULE_NOT_ENTITLED` rejection named.
1986
+ *
1987
+ * Every entry is kept, as a raw string: one call can need more than one
1988
+ * module, and a key this build predates is still the only part of the
1989
+ * payload a customer can act on.
1990
+ *
1991
+ * @category Transport
1992
+ * @hidden
1993
+ */
1994
+ const extractNotEntitledModules = (data) => {
1995
+ const { modules } = data !== null && data !== void 0 ? data : {};
1996
+ if (!Array.isArray(modules))
1997
+ return [];
1998
+ return modules.filter((module) => typeof module === 'string');
1999
+ };
1962
2000
  /**
1963
2001
  * Unwraps v3 server payload (for error handling and type extraction)
1964
2002
  *
@@ -1978,6 +2016,15 @@ const unwrapPayload = (response) => {
1978
2016
  throw new ASCUnknownError(response.code);
1979
2017
  }
1980
2018
  else if (response.status === 'error') {
2019
+ /*
2020
+ * An entitlement refusal must not arrive as a generic failure: a customer
2021
+ * cannot then tell a module they have not bought from an outage. Keyed on
2022
+ * the app code, never on the HTTP status, so a role or membership problem
2023
+ * still maps to the generic error.
2024
+ */
2025
+ if (response.code === 400325 /* Amity.ServerError.MODULE_NOT_ENTITLED */) {
2026
+ throw new AmityModuleNotEntitledError(response.message, extractNotEntitledModules(response.data));
2027
+ }
1981
2028
  throw new ASCApiError(response.message, response.code, "error" /* Amity.ErrorLevel.ERROR */);
1982
2029
  }
1983
2030
  // unjust, but helps force uncast "T | undefined" to allow deconstruction without warnings
@@ -5527,6 +5574,95 @@ const getForYouFeedSetting = async () => {
5527
5574
  };
5528
5575
  /* end_public_function */
5529
5576
 
5577
+ const ENFORCEMENT_MODES = ['off', 'shadow', 'enforce'];
5578
+ /**
5579
+ * An unrecognised mode decodes to `off`, and so does an absent one. A mode
5580
+ * this build does not know must not withhold anything — defaulting to
5581
+ * `enforce` would let a server-side rename black out a customer's app.
5582
+ *
5583
+ * @category private
5584
+ */
5585
+ const parseEnforcement = (mode) => {
5586
+ const match = ENFORCEMENT_MODES.find(value => value === mode);
5587
+ return match !== null && match !== void 0 ? match : 'off';
5588
+ };
5589
+ const parseGrants = (modules) => {
5590
+ const grants = {};
5591
+ Object.entries(modules !== null && modules !== void 0 ? modules : {}).forEach(([key, granted]) => {
5592
+ grants[key] = granted === true;
5593
+ });
5594
+ return grants;
5595
+ };
5596
+ /**
5597
+ * Every catalog entry survives, including one whose key this build has never
5598
+ * heard of, so a module added server-side is still listable.
5599
+ *
5600
+ * @category private
5601
+ */
5602
+ const parseCatalog = (catalog) => {
5603
+ const parsed = {};
5604
+ Object.entries(catalog !== null && catalog !== void 0 ? catalog : {}).forEach(([key, definition]) => {
5605
+ const rawRequires = definition === null || definition === void 0 ? void 0 : definition.requires;
5606
+ const requires = Array.isArray(rawRequires)
5607
+ ? rawRequires.filter((entry) => typeof entry === 'string')
5608
+ : [];
5609
+ parsed[key] = {
5610
+ label: typeof (definition === null || definition === void 0 ? void 0 : definition.label) === 'string' ? definition.label : key,
5611
+ kind: (definition === null || definition === void 0 ? void 0 : definition.kind) === 'setting' ? 'setting' : 'api',
5612
+ requires,
5613
+ };
5614
+ });
5615
+ return parsed;
5616
+ };
5617
+ /**
5618
+ * Decodes `GET /api/v3/network-settings/modules`. Nothing is trimmed: the
5619
+ * catalog is the dependency table callers resolve against, and a client that
5620
+ * cannot see it has to hardcode one.
5621
+ *
5622
+ * @category private
5623
+ */
5624
+ const parseModuleSettings = (raw) => {
5625
+ var _a;
5626
+ return ({
5627
+ enforcement: parseEnforcement((_a = raw === null || raw === void 0 ? void 0 : raw.enforcement) !== null && _a !== void 0 ? _a : raw === null || raw === void 0 ? void 0 : raw.mode),
5628
+ modules: parseGrants(raw === null || raw === void 0 ? void 0 : raw.modules),
5629
+ catalog: parseCatalog(raw === null || raw === void 0 ? void 0 : raw.catalog),
5630
+ });
5631
+ };
5632
+
5633
+ /* begin_public_function
5634
+ id: client.getModuleSettings
5635
+ */
5636
+ /**
5637
+ * ```js
5638
+ * import { Client } from '@amityco/ts-sdk'
5639
+ * const moduleSettings = await Client.getModuleSettings()
5640
+ * ```
5641
+ *
5642
+ * Fetches the network's module entitlements: which modules the network was
5643
+ * granted, whether core is enforcing those grants, and the catalog of all
5644
+ * modules with their dependency chains.
5645
+ *
5646
+ * The payload is returned whole — `catalog` is the dependency table
5647
+ * {@link resolveModuleAvailability} reads, and a client that cannot see it has
5648
+ * to hardcode one.
5649
+ *
5650
+ * The read is **advisory**. It tells a host what to offer; under `enforce` the
5651
+ * backend refuses a call to an unowned module regardless of what was resolved
5652
+ * here, and that refusal arrives as `AmityModuleNotEntitledError`.
5653
+ *
5654
+ * @returns A Promise of {@link Amity.ModuleSettings} object
5655
+ *
5656
+ * @category Client API
5657
+ * @async
5658
+ */
5659
+ const getModuleSettings = async () => {
5660
+ const client = getActiveClient();
5661
+ const { data } = await client.http.get(`/api/v3/network-settings/modules`);
5662
+ return parseModuleSettings(data);
5663
+ };
5664
+ /* end_public_function */
5665
+
5530
5666
  const checkUserPermission = (userId, permission) => {
5531
5667
  var _a;
5532
5668
  const user = (_a = pullFromCache(['user', 'get', userId])) === null || _a === void 0 ? void 0 : _a.data;
@@ -9302,6 +9438,7 @@ const createClient = (apiKey, apiRegion = API_REGIONS.SG, { debugSession = DEFAU
9302
9438
  getMarkerSyncConsistentMode,
9303
9439
  getProductCatalogueSetting,
9304
9440
  getForYouFeedSetting,
9441
+ getModuleSettings,
9305
9442
  /**
9306
9443
  * Prefix for the deviceId key in the local storage or async storage.
9307
9444
  * This is allow user to have multiple SDK client and Mqtt client within the same app.
@@ -10391,6 +10528,92 @@ const getUserUnread = (callback) => {
10391
10528
  };
10392
10529
  };
10393
10530
 
10531
+ /**
10532
+ * ```js
10533
+ * import { Client, resolveModuleAvailability } from '@amityco/ts-sdk'
10534
+ *
10535
+ * const moduleSettings = await Client.getModuleSettings()
10536
+ *
10537
+ * if (resolveModuleAvailability(moduleSettings, 'live').isAvailable) {
10538
+ * // offer the Live entry point
10539
+ * }
10540
+ * ```
10541
+ *
10542
+ * Resolves whether one module is usable on this network.
10543
+ *
10544
+ * Availability is a function of all three fields of
10545
+ * {@link Amity.ModuleSettings} — the enforcement mode, the module's own grant,
10546
+ * and its prerequisite chain — combined in that order. Reading `modules`
10547
+ * directly contradicts the mode: staging answers `mode: 'off'` with
10548
+ * `modules: {}`, and every module still works.
10549
+ *
10550
+ * The dependency table comes from the payload's `catalog`, never from a
10551
+ * hardcoded copy, so a chain the backend changes takes effect without an SDK
10552
+ * release.
10553
+ *
10554
+ * The SDK does not hold the settings — the caller keeps what it fetched and
10555
+ * resolves against it. With no settings at all (a fetch that never succeeded)
10556
+ * the caller reads that as every module available: under `enforce` the backend
10557
+ * refuses the calls anyway, so the exposure is a screen that offers too much,
10558
+ * not one that withholds what a customer paid for.
10559
+ *
10560
+ * @param moduleSettings The network's module settings
10561
+ * @param moduleKey The module to resolve — an {@link Amity.ModuleKey}, so the
10562
+ * 15 keys this build knows complete and type-check while a catalog entry it
10563
+ * does not know still resolves.
10564
+ * @returns An {@link Amity.ModuleAvailability} answer
10565
+ *
10566
+ * @category Client API
10567
+ */
10568
+ const resolveModuleAvailability = (moduleSettings, moduleKey, seen = new Set()) => {
10569
+ var _a, _b, _c, _d, _e;
10570
+ const isGranted = (_a = moduleSettings.modules[moduleKey]) !== null && _a !== void 0 ? _a : false;
10571
+ /*
10572
+ * Not enforcing: nothing is withheld, and no graph walk is needed. This has
10573
+ * to come first — `off` and `shadow` resolve available whatever the grant
10574
+ * says, and a cycle would otherwise return unavailable under `off` too.
10575
+ */
10576
+ if (moduleSettings.enforcement !== 'enforce') {
10577
+ return { moduleKey, isAvailable: true, isGranted };
10578
+ }
10579
+ // A cycle in `requires` resolves unavailable rather than recursing.
10580
+ if (seen.has(moduleKey)) {
10581
+ return {
10582
+ moduleKey,
10583
+ isAvailable: false,
10584
+ isGranted,
10585
+ reason: 'prerequisiteUnavailable',
10586
+ unsatisfied: (_c = (_b = moduleSettings.catalog[moduleKey]) === null || _b === void 0 ? void 0 : _b.requires) !== null && _c !== void 0 ? _c : [],
10587
+ };
10588
+ }
10589
+ // The module's own grant is evaluated before its prerequisites, so
10590
+ // `notGranted` is reported in preference when both hold.
10591
+ if (!isGranted) {
10592
+ return { moduleKey, isAvailable: false, isGranted, reason: 'notGranted' };
10593
+ }
10594
+ const requires = (_e = (_d = moduleSettings.catalog[moduleKey]) === null || _d === void 0 ? void 0 : _d.requires) !== null && _e !== void 0 ? _e : [];
10595
+ if (requires.length === 0)
10596
+ return { moduleKey, isAvailable: true, isGranted };
10597
+ // `requires` is satisfied by any one entry, transitively.
10598
+ const isSatisfied = requires.some(dependency => resolveModuleAvailability(moduleSettings, dependency, new Set([...seen, moduleKey]))
10599
+ .isAvailable);
10600
+ if (isSatisfied)
10601
+ return { moduleKey, isAvailable: true, isGranted };
10602
+ /*
10603
+ * `unsatisfied` carries the whole `requires` list rather than one entry. Any
10604
+ * one of them would have satisfied the requirement, so naming a single
10605
+ * module misdirects: telling a Community-only customer "Comment needs post"
10606
+ * hides that Story would also have done.
10607
+ */
10608
+ return {
10609
+ moduleKey,
10610
+ isAvailable: false,
10611
+ isGranted,
10612
+ reason: 'prerequisiteUnavailable',
10613
+ unsatisfied: requires,
10614
+ };
10615
+ };
10616
+
10394
10617
  var index$t = /*#__PURE__*/Object.freeze({
10395
10618
  __proto__: null,
10396
10619
  getActiveClient: getActiveClient,
@@ -10417,6 +10640,7 @@ var index$t = /*#__PURE__*/Object.freeze({
10417
10640
  getShareableLinkConfiguration: getShareableLinkConfiguration,
10418
10641
  getProductCatalogueSetting: getProductCatalogueSetting,
10419
10642
  getForYouFeedSetting: getForYouFeedSetting,
10643
+ getModuleSettings: getModuleSettings,
10420
10644
  loginAsVisitor: loginAsVisitor,
10421
10645
  loginAsBot: loginAsBot,
10422
10646
  getCurrentUser: getCurrentUser,
@@ -10444,7 +10668,8 @@ var index$t = /*#__PURE__*/Object.freeze({
10444
10668
  startMarkerSync: startMarkerSync,
10445
10669
  startUnreadSync: startUnreadSync,
10446
10670
  stopUnreadSync: stopUnreadSync,
10447
- getMarkerSyncConsistentMode: getMarkerSyncConsistentMode
10671
+ getMarkerSyncConsistentMode: getMarkerSyncConsistentMode,
10672
+ resolveModuleAvailability: resolveModuleAvailability
10448
10673
  });
10449
10674
 
10450
10675
  /* begin_public_function
@@ -37052,4 +37277,4 @@ class AmityAttachmentProductTags {
37052
37277
  }
37053
37278
  }
37054
37279
 
37055
- export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmitySharableContentType, AnalyticsSourceTypeEnum, index$g as CategoryRepository, index$k as ChannelRepository, index$t as Client, index$f as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$h as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, index as CuratedContentRepository, DefaultCommunityPostSetting, index$2 as EventRepository, FeedDataTypeEnum, index$e as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$q as FileRepository, FileType, GET_WATCHER_URLS, index$4 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$3 as LiveReactionRepository, index$8 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$o as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$9 as PollRepository, PostContentType, index$c as PostRepository, PostStructureType, index$1 as ProductRepository, index$p as ReactionRepository, index$a as RoomPresenceRepository, index$d as RoomRepository, SearchUsersByEnum, index$7 as StoryRepository, index$b as StreamRepository, index$n as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserReportTypeEnum, index$r as UserRepository, UserTypeEnum, VERSION, VideoResolution, VideoSize, VideoTranscodingStatus, backupCache, createQuery, createReport, createUserToken, deleteReport, disableCache, dropFromCache, enableCache, exceedsUntilAtBoundary, filterByChannelMembership, filterByCommunityMembership, filterByFeedType, filterByPostDataTypes, filterByPropEquality, filterByPropInclusion, filterByPropIntersection, filterBySearchTerm, filterByStringComparePartially, filterByUntilAt, filterCommentsByBlockUsers, filterPostsByBlockUsers, getBlockUsersId, getChannelTopic, getCommentTopic, getCommunityStoriesTopic, getCommunityTopic, getLiveReactionTopic, getLiveStreamTopic, getMarkedMessageTopic, getMarkerUserFeedTopic, getMessageTopic, getMyFollowersTopic, getMyFollowingsTopic, getNetworkId, getNetworkTopic, getPostTopic, getRole, getRoomStreamerTopic, getRoomWatcherTopic, getSmartFeedChannelTopic, getSmartFeedMessageTopic, getSmartFeedSubChannelTopic, getStoryTopic, getSubChannelTopic, getUserTopic, isAfterBefore, isAfterBeforeRaw, isCachable, isFetcher, isFresh, isLocal, isMutator, isOffline, isPaged, isReportedByMe, isSkip, mergeInCache, index$5 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };
37280
+ export { API_REGIONS, index$6 as AdRepository, AmityAttachmentProductTags, AmityAutoSubscription, AmityChannelNotificationModeEnum, AmityCommunityType, AmityEventOrderOption, AmityEventOriginType, AmityEventResponseStatus, AmityEventSortOption, AmityEventStatus, AmityEventType, AmityLiveViewerCountMode, AmityModuleNotEntitledError, AmitySharableContentType, AnalyticsSourceTypeEnum, index$g as CategoryRepository, index$k as ChannelRepository, index$t as Client, index$f as CommentRepository, CommunityNotificationEventNameEnum, CommunityPostSettingMaps, CommunityPostSettings, index$h as CommunityRepository, ContentFeedType, ContentFlagReasonEnum, index as CuratedContentRepository, DefaultCommunityPostSetting, index$2 as EventRepository, FeedDataTypeEnum, index$e as FeedRepository, FeedSortByEnum, FeedSourceEnum, FeedTypeEnum, FileAccessTypeEnum, index$q as FileRepository, FileType, GET_WATCHER_URLS, index$4 as InvitationRepository, InvitationSortByEnum, InvitationStatusEnum, InvitationTargetTypeEnum, InvitationTypeEnum, JoinRequestStatusEnum, JoinResultStatusEnum, index$3 as LiveReactionRepository, index$8 as LiveStreamPlayer, MembershipAcceptanceTypeEnum, MessageContentType, index$o as MessageRepository, NotificationRolesFilterTypeEnum, NotificationSettingsLevelEnum, index$9 as PollRepository, PostContentType, index$c as PostRepository, PostStructureType, index$1 as ProductRepository, index$p as ReactionRepository, index$a as RoomPresenceRepository, index$d as RoomRepository, SearchUsersByEnum, index$7 as StoryRepository, index$b as StreamRepository, index$n as SubChannelRepository, SubscriptionLevels, UserNotificationModuleNameEnum, UserReportTypeEnum, index$r as UserRepository, UserTypeEnum, VERSION, VideoResolution, VideoSize, VideoTranscodingStatus, backupCache, createQuery, createReport, createUserToken, deleteReport, disableCache, dropFromCache, enableCache, exceedsUntilAtBoundary, filterByChannelMembership, filterByCommunityMembership, filterByFeedType, filterByPostDataTypes, filterByPropEquality, filterByPropInclusion, filterByPropIntersection, filterBySearchTerm, filterByStringComparePartially, filterByUntilAt, filterCommentsByBlockUsers, filterPostsByBlockUsers, getBlockUsersId, getChannelTopic, getCommentTopic, getCommunityStoriesTopic, getCommunityTopic, getLiveReactionTopic, getLiveStreamTopic, getMarkedMessageTopic, getMarkerUserFeedTopic, getMessageTopic, getMyFollowersTopic, getMyFollowingsTopic, getNetworkId, getNetworkTopic, getPostTopic, getRole, getRoomStreamerTopic, getRoomWatcherTopic, getSmartFeedChannelTopic, getSmartFeedMessageTopic, getSmartFeedSubChannelTopic, getStoryTopic, getSubChannelTopic, getUserTopic, isAfterBefore, isAfterBeforeRaw, isCachable, isFetcher, isFresh, isLocal, isMutator, isOffline, isPaged, isReportedByMe, isSkip, mergeInCache, index$5 as notificationTray, onChannelMarkerFetched, onFeedMarkerFetched, onFeedMarkerUpdated, onMessageMarked, onMessageMarkerFetched, onSubChannelMarkerFetched, onSubChannelMarkerUpdated, onUserMarkerFetched, onUserMarkerFetchedLegacy, pullFromCache, pushToCache, queryCache, queryOptions, queryRoles, resolveModuleAvailability, restoreCache, runQuery, sortByChannelSegment, sortByDisplayName, sortByFirstCreated, sortByFirstUpdated, sortByLastActivity, sortByLastCreated, sortByLastUpdated, sortByLocalSortingDate, sortByName, sortBySegmentNumber, subscribeTopic, toPage, toPageRaw, toToken, upsertInCache, validateUntilAt, wipeCache };