@namiml/sdk-core 3.4.12-dev.202609241021 → 3.4.12-dev.202609251500

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/dist/index.cjs CHANGED
@@ -106,7 +106,7 @@ const {
106
106
  // version — stamped by scripts/version.sh
107
107
  NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.4.12",
108
108
  // full package version including dev suffix — stamped by scripts/version.sh
109
- NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609241021",
109
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609251500",
110
110
  // environments
111
111
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
112
112
  // error messages
@@ -116,7 +116,7 @@ AUTH_DEVICE: exports.AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION: expor
116
116
  // API settings
117
117
  API_VERSION: exports.API_VERSION = "v3", BASE_URL_PATH: exports.BASE_URL_PATH = `sdk/${exports.API_VERSION}/platform`, BASE_URL: exports.BASE_URL = "https://app.namiml.com", BASE_STAGING_URL: exports.BASE_STAGING_URL = "https://app-staging.namiml.com", CUSTOM_HOST_PREFIX: exports.CUSTOM_HOST_PREFIX = "namiAPIHost=", USE_STAGING_API: exports.USE_STAGING_API = "useStagingAPI",
118
118
  // // extended client info
119
- EXTENDED_CLIENT_INFO_PREFIX: exports.EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER: exports.EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS: exports.VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS: exports.LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC: exports.LOG_HTTP_TRAFFIC = "logHTTPTraffic", STARTUP_TELEMETRY: exports.STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM: exports.EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION: exports.EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT: exports.API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC: exports.API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT: exports.API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT: exports.DEVICE_API_TIMEOUT_LIMIT = 2000,
119
+ EXTENDED_CLIENT_INFO_PREFIX: exports.EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER: exports.EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS: exports.VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS: exports.LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC: exports.LOG_HTTP_TRAFFIC = "logHTTPTraffic", API_TRAFFIC_OBSERVER: exports.API_TRAFFIC_OBSERVER = "apiTrafficObserver", STARTUP_TELEMETRY: exports.STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM: exports.EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION: exports.EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT: exports.API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC: exports.API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT: exports.API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT: exports.DEVICE_API_TIMEOUT_LIMIT = 2000,
120
120
  // status codes
121
121
  STATUS_SUCCESS: exports.STATUS_SUCCESS = 200, STATUS_BAD_REQUEST: exports.STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND: exports.STATUS_NOT_FOUND = 404, STATUS_CONFLICT: exports.STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR: exports.STATUS_INTERNAL_SERVER_ERROR = 500,
122
122
  // configuration states
@@ -6845,6 +6845,17 @@ const shouldLogHTTPTraffic = () => {
6845
6845
  const namiCommands = storageService.getNamiConfig()?.namiCommands;
6846
6846
  return namiCommands?.includes(exports.LOG_HTTP_TRAFFIC) ?? false;
6847
6847
  };
6848
+ /**
6849
+ * Whether the SDK should build traffic records for the registered API traffic observer.
6850
+ *
6851
+ * Read per attempt at capture time, never once at registration: hosts register the
6852
+ * observer before `Nami.configure()`, so a registration-time read would see an
6853
+ * unpopulated config and disable capture for the life of the process.
6854
+ */
6855
+ const shouldObserveAPITraffic = () => {
6856
+ const namiCommands = storageService.getNamiConfig()?.namiCommands;
6857
+ return namiCommands?.includes(exports.API_TRAFFIC_OBSERVER) ?? false;
6858
+ };
6848
6859
  function tryParseJson(str) {
6849
6860
  const trimmed = str.trim();
6850
6861
  if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
@@ -6879,6 +6890,166 @@ function base64ToUint8(b64) {
6879
6890
  return bytes;
6880
6891
  }
6881
6892
 
6893
+ /** Maximum number of UTF-8 characters retained from a request or response body. */
6894
+ const BODY_LIMIT = 4096;
6895
+ /**
6896
+ * Sentinel `statusCode` for a request that never produced an HTTP response.
6897
+ *
6898
+ * No HTTP status is ever 0, so it cannot collide with a real one. Prefer
6899
+ * {@link didReceiveResponse} over comparing against this directly.
6900
+ */
6901
+ const TRANSPORT_FAILURE_STATUS = 0;
6902
+ /**
6903
+ * `true` when the attempt completed with an HTTP response, `false` when it failed in
6904
+ * transport. Reads better than `statusCode !== 0` and keeps the sentinel an implementation
6905
+ * detail.
6906
+ */
6907
+ function didReceiveResponse(record) {
6908
+ return record.statusCode !== TRANSPORT_FAILURE_STATUS;
6909
+ }
6910
+ let observer = null;
6911
+ /**
6912
+ * Registers the traffic observer, or clears it when passed `null`.
6913
+ *
6914
+ * Intended for debug and QA tooling. A single observer is held — registering a second
6915
+ * replaces the first, so a host that needs several should fan out itself.
6916
+ *
6917
+ * Capture additionally requires the `apiTrafficObserver` Nami command. That check happens
6918
+ * at capture time rather than here on purpose: hosts register before `Nami.configure()`,
6919
+ * so a registration-time gate would read an unpopulated config and silently no-op for the
6920
+ * life of the process.
6921
+ */
6922
+ function registerAPITrafficObserver(next) {
6923
+ observer = next;
6924
+ }
6925
+ /**
6926
+ * Whether a record should be built for this attempt.
6927
+ *
6928
+ * Both conditions are required, and both are evaluated per attempt so that enabling the
6929
+ * command mid-session takes effect and an unobserved host pays nothing.
6930
+ */
6931
+ function capturing() {
6932
+ return observer !== null && shouldObserveAPITraffic();
6933
+ }
6934
+ function truncate(body) {
6935
+ if (!body)
6936
+ return undefined;
6937
+ return body.length > BODY_LIMIT ? body.slice(0, BODY_LIMIT) : body;
6938
+ }
6939
+ /**
6940
+ * The path portion of `url`, with its trailing slash intact.
6941
+ *
6942
+ * Every Nami endpoint is a trailing-slash route, so losing it renders `/device/` as
6943
+ * `/device` — which is what Apple's port of this shipped before it was caught
6944
+ * (NAM-4446). `URL.pathname` keeps the slash; the explicit test for it is what stops a
6945
+ * future "tidy up the URL parsing" change from reintroducing the bug.
6946
+ *
6947
+ * Falls back to the raw string for anything unparseable rather than throwing: a record
6948
+ * with an odd path is far more useful than an exception raised inside instrumentation.
6949
+ */
6950
+ function pathOf(url) {
6951
+ try {
6952
+ return new URL(url).pathname;
6953
+ }
6954
+ catch {
6955
+ return url;
6956
+ }
6957
+ }
6958
+ /** The request body as a string, when it is one we can cheaply render. */
6959
+ function requestBodyOf(options) {
6960
+ const body = options?.body;
6961
+ return typeof body === "string" ? truncate(body) : undefined;
6962
+ }
6963
+ function emit(record) {
6964
+ try {
6965
+ observer?.(record);
6966
+ }
6967
+ catch {
6968
+ // An observer that throws is a bug in the host's debug tooling, not a reason to fail
6969
+ // the request that was being observed.
6970
+ }
6971
+ }
6972
+ /**
6973
+ * Runs `doFetch` and reports the attempt to the registered observer.
6974
+ *
6975
+ * Wraps both of the SDK's fetch sites — the API path in `withRetry` and the CDN path in
6976
+ * `cdnFetch` — so timing, truncation and the transport-failure sentinel are defined once.
6977
+ *
6978
+ * Two properties matter and are easy to lose:
6979
+ *
6980
+ * - **The record is emitted before the caller branches on status.** `withRetry` throws
6981
+ * `ConflictError` on 409 and `RetryLimitExceededError` once retries are spent, and
6982
+ * `cdnFetch` throws `NonRetryableHttpError`; recording after any of those would drop the
6983
+ * record for exactly the failures worth seeing.
6984
+ * - **Transport failures are recorded and rethrown unchanged.** `fetch` rejects with a
6985
+ * `TypeError` for DNS/TCP/TLS/CORS failures and an `AbortError` on timeout, and neither
6986
+ * ever becomes a `Response`. A hook that only saw responses structurally could not report
6987
+ * a connection that never opened — which is most of why this tab exists. (Android reached
6988
+ * the same conclusion choosing OkHttp's application interceptor over the network one,
6989
+ * NAM-4447.)
6990
+ *
6991
+ * The response is returned undisturbed: the body is read from a `clone()`, so callers still
6992
+ * get an unconsumed stream.
6993
+ */
6994
+ async function observeFetch(url, options, kind, doFetch) {
6995
+ if (!capturing())
6996
+ return doFetch();
6997
+ const method = options?.method ?? "GET";
6998
+ const startedAt = Date.now();
6999
+ let response;
7000
+ try {
7001
+ response = await doFetch();
7002
+ }
7003
+ catch (error) {
7004
+ emit({
7005
+ method,
7006
+ url,
7007
+ path: pathOf(url),
7008
+ statusCode: TRANSPORT_FAILURE_STATUS,
7009
+ durationMs: Date.now() - startedAt,
7010
+ timestamp: new Date(),
7011
+ requestBody: requestBodyOf(options),
7012
+ errorDescription: describeError(error),
7013
+ kind,
7014
+ });
7015
+ throw error;
7016
+ }
7017
+ const durationMs = Date.now() - startedAt;
7018
+ let responseBody;
7019
+ try {
7020
+ responseBody = truncate(await response.clone().text());
7021
+ }
7022
+ catch {
7023
+ // A body that cannot be re-read (already disturbed, or an opaque response) is not
7024
+ // worth failing the request over — the status line is still useful on its own.
7025
+ }
7026
+ emit({
7027
+ method,
7028
+ url,
7029
+ path: pathOf(url),
7030
+ statusCode: response.status,
7031
+ durationMs,
7032
+ timestamp: new Date(),
7033
+ requestBody: requestBodyOf(options),
7034
+ responseBody,
7035
+ kind,
7036
+ });
7037
+ return response;
7038
+ }
7039
+ /**
7040
+ * A human-readable description of a thrown fetch failure.
7041
+ *
7042
+ * Never returns undefined for a caught error: a record carrying the transport-failure
7043
+ * sentinel and no reason reads as "no response, and no explanation either", which is worse
7044
+ * than a generic string.
7045
+ */
7046
+ function describeError(error) {
7047
+ if (error instanceof Error) {
7048
+ return error.name ? `${error.name}: ${error.message}` : error.message;
7049
+ }
7050
+ return String(error);
7051
+ }
7052
+
6882
7053
  async function withRetry(url, options, timeout = exports.API_TIMEOUT_LIMIT, retries = exports.API_MAX_CALLS_LIMIT) {
6883
7054
  let retryCount = 0;
6884
7055
  const logRequests = shouldLogHTTPRequests();
@@ -6891,7 +7062,10 @@ async function withRetry(url, options, timeout = exports.API_TIMEOUT_LIMIT, retr
6891
7062
  if (logTraffic && options?.body) {
6892
7063
  logger.debug(`[HTTP] Request body: ${options.body}`);
6893
7064
  }
6894
- const response = await timeoutRequest$1(url, options, timeout);
7065
+ // One record per attempt, emitted before the status branches below: `withRetry`
7066
+ // throws on 409 and on exhausted retries, so recording afterwards would lose the
7067
+ // record for exactly the failures worth seeing (NAM-4606).
7068
+ const response = await observeFetch(url, options, "api", () => timeoutRequest$1(url, options, timeout));
6895
7069
  if (!response.ok) {
6896
7070
  if (logTraffic) {
6897
7071
  const errorBody = await response.clone().text();
@@ -57666,7 +57840,9 @@ async function timeoutRequest(url, timeout) {
57666
57840
  const controller = new AbortController();
57667
57841
  const timeoutId = setTimeout(() => controller.abort(), timeout);
57668
57842
  try {
57669
- return await fetch(url, { signal: controller.signal });
57843
+ // Recorded as `cdn` rather than `api`. Without this the per-paywall body traffic is
57844
+ // invisible to the traffic tab, which is the traffic NAM-4357 is about (NAM-4606).
57845
+ return await observeFetch(url, { method: "GET" }, "cdn", () => fetch(url, { signal: controller.signal }));
57670
57846
  }
57671
57847
  finally {
57672
57848
  clearTimeout(timeoutId);
@@ -65874,6 +66050,7 @@ exports.SHOULD_SHOW_LOADING_INDICATOR = SHOULD_SHOW_LOADING_INDICATOR;
65874
66050
  exports.SessionService = SessionService;
65875
66051
  exports.SimpleEventTarget = SimpleEventTarget;
65876
66052
  exports.StorageService = StorageService;
66053
+ exports.TRANSPORT_FAILURE_STATUS = TRANSPORT_FAILURE_STATUS;
65877
66054
  exports._NamiProfileManager = NamiProfileManager$1;
65878
66055
  exports._internal = internal;
65879
66056
  exports.activateEntitlementByPurchase = activateEntitlementByPurchase;
@@ -65892,6 +66069,7 @@ exports.convertLocale = convertLocale;
65892
66069
  exports.convertOfferToPricingPhase = convertOfferToPricingPhase;
65893
66070
  exports.createNamiEntitlements = createNamiEntitlements;
65894
66071
  exports.currentSku = currentSku;
66072
+ exports.didReceiveResponse = didReceiveResponse;
65895
66073
  exports.empty = empty;
65896
66074
  exports.extractStandardPricingPhases = extractStandardPricingPhases;
65897
66075
  exports.formatDate = formatDate;
@@ -65948,6 +66126,7 @@ exports.normalizeLaunchContext = normalizeLaunchContext;
65948
66126
  exports.parseToSemver = parseToSemver;
65949
66127
  exports.postConversion = postConversion;
65950
66128
  exports.productDetail = productDetail;
66129
+ exports.registerAPITrafficObserver = registerAPITrafficObserver;
65951
66130
  exports.registerPlatformAdapters = registerPlatformAdapters;
65952
66131
  exports.registerPurchaseAdapter = registerPurchaseAdapter;
65953
66132
  exports.resolveLoopSource = resolveLoopSource;
package/dist/index.d.ts CHANGED
@@ -2999,6 +2999,78 @@ declare class StorageService {
2999
2999
  }
3000
3000
  declare const storageService: StorageService;
3001
3001
 
3002
+ /**
3003
+ * Sentinel `statusCode` for a request that never produced an HTTP response.
3004
+ *
3005
+ * No HTTP status is ever 0, so it cannot collide with a real one. Prefer
3006
+ * {@link didReceiveResponse} over comparing against this directly.
3007
+ */
3008
+ declare const TRANSPORT_FAILURE_STATUS = 0;
3009
+ /** Which network path produced a {@link NamiAPITrafficRecord}. */
3010
+ type NamiAPITrafficKind = "api" | "cdn";
3011
+ /**
3012
+ * One completed request attempt observed by the SDK.
3013
+ *
3014
+ * Emitted once per attempt: a retried request produces one record per try rather than a
3015
+ * single collapsed entry, because retry behaviour is itself frequently what is being
3016
+ * debugged.
3017
+ *
3018
+ * Request and response bodies are captured verbatim to a 4 KB prefix, and some Nami
3019
+ * endpoints carry sensitive payloads — `PUT /login/` sends the host-supplied `external_id`
3020
+ * (often an email), and `POST /transaction/` sends purchase amounts and identifiers.
3021
+ * Records are for local debugging only: do not persist them, ship them off-device, or
3022
+ * render them anywhere they could be screenshotted into a support ticket.
3023
+ *
3024
+ * HTTP headers are deliberately not captured. This is data minimisation, **not** a
3025
+ * credential defence — this SDK sends no authorization header at all, and the platform and
3026
+ * device identifiers that authorize a request live in the URL path, which *is* captured.
3027
+ */
3028
+ interface NamiAPITrafficRecord {
3029
+ /** HTTP method, for example `POST`. */
3030
+ method: string;
3031
+ /** Absolute request URL, including any query string. */
3032
+ url: string;
3033
+ /** Request path, without scheme, host, or query string. Trailing slash preserved. */
3034
+ path: string;
3035
+ /**
3036
+ * HTTP status code, or {@link TRANSPORT_FAILURE_STATUS} when the request never produced
3037
+ * a response.
3038
+ */
3039
+ statusCode: number;
3040
+ /** Wall-clock duration of this attempt, in milliseconds. */
3041
+ durationMs: number;
3042
+ /** When the response — or the failure — was observed. */
3043
+ timestamp: Date;
3044
+ /** UTF-8 request body, truncated to 4 KB. Absent when the request carried no body. */
3045
+ requestBody?: string;
3046
+ /** UTF-8 response body, truncated to 4 KB. Absent when the response carried no body. */
3047
+ responseBody?: string;
3048
+ /** Description of the transport error, when one occurred. */
3049
+ errorDescription?: string;
3050
+ /** Which network path produced this record. */
3051
+ kind: NamiAPITrafficKind;
3052
+ }
3053
+ /**
3054
+ * `true` when the attempt completed with an HTTP response, `false` when it failed in
3055
+ * transport. Reads better than `statusCode !== 0` and keeps the sentinel an implementation
3056
+ * detail.
3057
+ */
3058
+ declare function didReceiveResponse(record: NamiAPITrafficRecord): boolean;
3059
+ /** Receives one {@link NamiAPITrafficRecord} per completed request attempt. */
3060
+ type NamiAPITrafficObserver = (record: NamiAPITrafficRecord) => void;
3061
+ /**
3062
+ * Registers the traffic observer, or clears it when passed `null`.
3063
+ *
3064
+ * Intended for debug and QA tooling. A single observer is held — registering a second
3065
+ * replaces the first, so a host that needs several should fan out itself.
3066
+ *
3067
+ * Capture additionally requires the `apiTrafficObserver` Nami command. That check happens
3068
+ * at capture time rather than here on purpose: hosts register before `Nami.configure()`,
3069
+ * so a registration-time gate would read an unpopulated config and silently no-op for the
3070
+ * life of the process.
3071
+ */
3072
+ declare function registerAPITrafficObserver(next: NamiAPITrafficObserver | null): void;
3073
+
3002
3074
  declare class Logger {
3003
3075
  private currentLogLevel;
3004
3076
  setLogger: (level?: NamiLogLevel) => void;
@@ -3107,6 +3179,7 @@ declare const EXTENDED_CLIENT_INFO_DELIMITER: string;
3107
3179
  declare const VALIDATE_PRODUCT_GROUPS: string;
3108
3180
  declare const LOG_HTTP_REQUESTS: string;
3109
3181
  declare const LOG_HTTP_TRAFFIC: string;
3182
+ declare const API_TRAFFIC_OBSERVER: string;
3110
3183
  declare const STARTUP_TELEMETRY: string;
3111
3184
  declare const EXTENDED_PLATFORM: string;
3112
3185
  declare const EXTENDED_PLATFORM_VERSION: string;
@@ -4042,5 +4115,5 @@ declare namespace internal {
4042
4115
  };
4043
4116
  }
4044
4117
 
4045
- export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$2 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$2 as NamiCustomerManager, NamiEntitlementManager$2 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$2 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$2 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
4046
- export type { AccountStateHandler$1 as AccountStateHandler, AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, FormFieldValidator, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, JourneySettings, LoginResponse, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignManagerStatic, NamiCampaignSegment, NamiCompletionResult, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiCustomerManagerStatic, NamiEntitlement$1 as NamiEntitlement, NamiEntitlementManagerStatic, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowCompletionCondition, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowManagerStatic, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiHandoffAttributeValue, NamiHandoffComplete, NamiHandoffOutcome, NamiHandoffTagValue, NamiInitialConfig, NamiKnownHandoffTag, NamiLanguageCodes, NamiLogLevel, NamiLoginSuccess, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPaywallManagerStatic, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchaseManagerStatic, NamiPurchaseSuccess, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, NamiUserDataEnvelope, None, NoneSpec, PaywallActionEvent, PaywallHandle, PaywallResultHandler, PaywallSKU, PricingPhase, ProductGroup, Pulse, PulseSpec, PurchaseContext, PurchaseResult, PurchaseValidationRequest, SKU, SKUActionHandler, ScreenInfo, Session, TBaseComponent, TButtonContainer, TCarouselContainer, TCarouselSlide, TCarouselSlidesState, TCollapseContainer, TComponent, TConditionalAttributes, TConditionalComponent, TContainer, TContainerPosition, TCountdownTimerTextComponent, TDevice, TDisabledButton, TField, TFieldSettings, TFlexProductContainer, THeaderFooter, TImageComponent, TInitialState, TMediaTypes, TOffer, TPages, TPaywallContext, TPaywallLaunchContext, TPaywallMedia, TPaywallTemplate, TPlayPauseButton, TProductContainer, TProductGroup, TProgressBarComponent, TProgressIndicatorComponent, TQRCodeComponent, TRadioButton, TRepeatingGrid, TResponsiveGrid, TSegmentPicker, TSegmentPickerItem, TSemverObj, TSlot, TSlotGroupSelector, TSpacerComponent, TStack, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextInputComponent, TTextInputType, TTextLikeComponent, TTextListComponent, TTimelineRail, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
4118
+ export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_TRAFFIC_OBSERVER, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$2 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$2 as NamiCustomerManager, NamiEntitlementManager$2 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$2 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$2 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, TRANSPORT_FAILURE_STATUS, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, didReceiveResponse, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
4119
+ export type { AccountStateHandler$1 as AccountStateHandler, AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, FormFieldValidator, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, JourneySettings, LoginResponse, NamiAPITrafficKind, NamiAPITrafficObserver, NamiAPITrafficRecord, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignManagerStatic, NamiCampaignSegment, NamiCompletionResult, NamiConfiguration, NamiConfigurationState, NamiCustomAttributeValue, NamiCustomerManagerStatic, NamiEntitlement$1 as NamiEntitlement, NamiEntitlementManagerStatic, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowCompletionCondition, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowManagerStatic, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiHandoffAttributeValue, NamiHandoffComplete, NamiHandoffOutcome, NamiHandoffTagValue, NamiInitialConfig, NamiKnownHandoffTag, NamiLanguageCodes, NamiLogLevel, NamiLoginSuccess, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPaywallManagerStatic, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchaseManagerStatic, NamiPurchaseSuccess, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, NamiUserDataEnvelope, None, NoneSpec, PaywallActionEvent, PaywallHandle, PaywallResultHandler, PaywallSKU, PricingPhase, ProductGroup, Pulse, PulseSpec, PurchaseContext, PurchaseResult, PurchaseValidationRequest, SKU, SKUActionHandler, ScreenInfo, Session, TBaseComponent, TButtonContainer, TCarouselContainer, TCarouselSlide, TCarouselSlidesState, TCollapseContainer, TComponent, TConditionalAttributes, TConditionalComponent, TContainer, TContainerPosition, TCountdownTimerTextComponent, TDevice, TDisabledButton, TField, TFieldSettings, TFlexProductContainer, THeaderFooter, TImageComponent, TInitialState, TMediaTypes, TOffer, TPages, TPaywallContext, TPaywallLaunchContext, TPaywallMedia, TPaywallTemplate, TPlayPauseButton, TProductContainer, TProductGroup, TProgressBarComponent, TProgressIndicatorComponent, TQRCodeComponent, TRadioButton, TRepeatingGrid, TResponsiveGrid, TSegmentPicker, TSegmentPickerItem, TSemverObj, TSlot, TSlotGroupSelector, TSpacerComponent, TStack, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextInputComponent, TTextInputType, TTextLikeComponent, TTextListComponent, TTimelineRail, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
package/dist/index.mjs CHANGED
@@ -104,7 +104,7 @@ const {
104
104
  // version — stamped by scripts/version.sh
105
105
  NAMI_SDK_VERSION = "3.4.12",
106
106
  // full package version including dev suffix — stamped by scripts/version.sh
107
- NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609241021",
107
+ NAMI_SDK_PACKAGE_VERSION = "3.4.12-dev.202609251500",
108
108
  // environments
109
109
  PRODUCTION = "production", DEVELOPMENT = "development",
110
110
  // error messages
@@ -114,7 +114,7 @@ AUTH_DEVICE = "nami_auth_device", NAMI_CONFIGURATION = "nami_configuration", NAM
114
114
  // API settings
115
115
  API_VERSION = "v3", BASE_URL_PATH = `sdk/${API_VERSION}/platform`, BASE_URL = "https://app.namiml.com", BASE_STAGING_URL = "https://app-staging.namiml.com", CUSTOM_HOST_PREFIX = "namiAPIHost=", USE_STAGING_API = "useStagingAPI",
116
116
  // // extended client info
117
- EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC = "logHTTPTraffic", STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT = 2000,
117
+ EXTENDED_CLIENT_INFO_PREFIX = "extendedClientInfo", EXTENDED_CLIENT_INFO_DELIMITER = ":", VALIDATE_PRODUCT_GROUPS = "validateProductGroups", LOG_HTTP_REQUESTS = "logHTTPRequests", LOG_HTTP_TRAFFIC = "logHTTPTraffic", API_TRAFFIC_OBSERVER = "apiTrafficObserver", STARTUP_TELEMETRY = "startupTelemetry", EXTENDED_PLATFORM = "extended-platform", EXTENDED_PLATFORM_VERSION = "extended-platform-version", API_MAX_CALLS_LIMIT = 2, API_RETRY_DELAY_SEC = 2, API_TIMEOUT_LIMIT = 20000, DEVICE_API_TIMEOUT_LIMIT = 2000,
118
118
  // status codes
119
119
  STATUS_SUCCESS = 200, STATUS_BAD_REQUEST = 400, STATUS_NOT_FOUND = 404, STATUS_CONFLICT = 409, STATUS_INTERNAL_SERVER_ERROR = 500,
120
120
  // configuration states
@@ -6843,6 +6843,17 @@ const shouldLogHTTPTraffic = () => {
6843
6843
  const namiCommands = storageService.getNamiConfig()?.namiCommands;
6844
6844
  return namiCommands?.includes(LOG_HTTP_TRAFFIC) ?? false;
6845
6845
  };
6846
+ /**
6847
+ * Whether the SDK should build traffic records for the registered API traffic observer.
6848
+ *
6849
+ * Read per attempt at capture time, never once at registration: hosts register the
6850
+ * observer before `Nami.configure()`, so a registration-time read would see an
6851
+ * unpopulated config and disable capture for the life of the process.
6852
+ */
6853
+ const shouldObserveAPITraffic = () => {
6854
+ const namiCommands = storageService.getNamiConfig()?.namiCommands;
6855
+ return namiCommands?.includes(API_TRAFFIC_OBSERVER) ?? false;
6856
+ };
6846
6857
  function tryParseJson(str) {
6847
6858
  const trimmed = str.trim();
6848
6859
  if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
@@ -6877,6 +6888,166 @@ function base64ToUint8(b64) {
6877
6888
  return bytes;
6878
6889
  }
6879
6890
 
6891
+ /** Maximum number of UTF-8 characters retained from a request or response body. */
6892
+ const BODY_LIMIT = 4096;
6893
+ /**
6894
+ * Sentinel `statusCode` for a request that never produced an HTTP response.
6895
+ *
6896
+ * No HTTP status is ever 0, so it cannot collide with a real one. Prefer
6897
+ * {@link didReceiveResponse} over comparing against this directly.
6898
+ */
6899
+ const TRANSPORT_FAILURE_STATUS = 0;
6900
+ /**
6901
+ * `true` when the attempt completed with an HTTP response, `false` when it failed in
6902
+ * transport. Reads better than `statusCode !== 0` and keeps the sentinel an implementation
6903
+ * detail.
6904
+ */
6905
+ function didReceiveResponse(record) {
6906
+ return record.statusCode !== TRANSPORT_FAILURE_STATUS;
6907
+ }
6908
+ let observer = null;
6909
+ /**
6910
+ * Registers the traffic observer, or clears it when passed `null`.
6911
+ *
6912
+ * Intended for debug and QA tooling. A single observer is held — registering a second
6913
+ * replaces the first, so a host that needs several should fan out itself.
6914
+ *
6915
+ * Capture additionally requires the `apiTrafficObserver` Nami command. That check happens
6916
+ * at capture time rather than here on purpose: hosts register before `Nami.configure()`,
6917
+ * so a registration-time gate would read an unpopulated config and silently no-op for the
6918
+ * life of the process.
6919
+ */
6920
+ function registerAPITrafficObserver(next) {
6921
+ observer = next;
6922
+ }
6923
+ /**
6924
+ * Whether a record should be built for this attempt.
6925
+ *
6926
+ * Both conditions are required, and both are evaluated per attempt so that enabling the
6927
+ * command mid-session takes effect and an unobserved host pays nothing.
6928
+ */
6929
+ function capturing() {
6930
+ return observer !== null && shouldObserveAPITraffic();
6931
+ }
6932
+ function truncate(body) {
6933
+ if (!body)
6934
+ return undefined;
6935
+ return body.length > BODY_LIMIT ? body.slice(0, BODY_LIMIT) : body;
6936
+ }
6937
+ /**
6938
+ * The path portion of `url`, with its trailing slash intact.
6939
+ *
6940
+ * Every Nami endpoint is a trailing-slash route, so losing it renders `/device/` as
6941
+ * `/device` — which is what Apple's port of this shipped before it was caught
6942
+ * (NAM-4446). `URL.pathname` keeps the slash; the explicit test for it is what stops a
6943
+ * future "tidy up the URL parsing" change from reintroducing the bug.
6944
+ *
6945
+ * Falls back to the raw string for anything unparseable rather than throwing: a record
6946
+ * with an odd path is far more useful than an exception raised inside instrumentation.
6947
+ */
6948
+ function pathOf(url) {
6949
+ try {
6950
+ return new URL(url).pathname;
6951
+ }
6952
+ catch {
6953
+ return url;
6954
+ }
6955
+ }
6956
+ /** The request body as a string, when it is one we can cheaply render. */
6957
+ function requestBodyOf(options) {
6958
+ const body = options?.body;
6959
+ return typeof body === "string" ? truncate(body) : undefined;
6960
+ }
6961
+ function emit(record) {
6962
+ try {
6963
+ observer?.(record);
6964
+ }
6965
+ catch {
6966
+ // An observer that throws is a bug in the host's debug tooling, not a reason to fail
6967
+ // the request that was being observed.
6968
+ }
6969
+ }
6970
+ /**
6971
+ * Runs `doFetch` and reports the attempt to the registered observer.
6972
+ *
6973
+ * Wraps both of the SDK's fetch sites — the API path in `withRetry` and the CDN path in
6974
+ * `cdnFetch` — so timing, truncation and the transport-failure sentinel are defined once.
6975
+ *
6976
+ * Two properties matter and are easy to lose:
6977
+ *
6978
+ * - **The record is emitted before the caller branches on status.** `withRetry` throws
6979
+ * `ConflictError` on 409 and `RetryLimitExceededError` once retries are spent, and
6980
+ * `cdnFetch` throws `NonRetryableHttpError`; recording after any of those would drop the
6981
+ * record for exactly the failures worth seeing.
6982
+ * - **Transport failures are recorded and rethrown unchanged.** `fetch` rejects with a
6983
+ * `TypeError` for DNS/TCP/TLS/CORS failures and an `AbortError` on timeout, and neither
6984
+ * ever becomes a `Response`. A hook that only saw responses structurally could not report
6985
+ * a connection that never opened — which is most of why this tab exists. (Android reached
6986
+ * the same conclusion choosing OkHttp's application interceptor over the network one,
6987
+ * NAM-4447.)
6988
+ *
6989
+ * The response is returned undisturbed: the body is read from a `clone()`, so callers still
6990
+ * get an unconsumed stream.
6991
+ */
6992
+ async function observeFetch(url, options, kind, doFetch) {
6993
+ if (!capturing())
6994
+ return doFetch();
6995
+ const method = options?.method ?? "GET";
6996
+ const startedAt = Date.now();
6997
+ let response;
6998
+ try {
6999
+ response = await doFetch();
7000
+ }
7001
+ catch (error) {
7002
+ emit({
7003
+ method,
7004
+ url,
7005
+ path: pathOf(url),
7006
+ statusCode: TRANSPORT_FAILURE_STATUS,
7007
+ durationMs: Date.now() - startedAt,
7008
+ timestamp: new Date(),
7009
+ requestBody: requestBodyOf(options),
7010
+ errorDescription: describeError(error),
7011
+ kind,
7012
+ });
7013
+ throw error;
7014
+ }
7015
+ const durationMs = Date.now() - startedAt;
7016
+ let responseBody;
7017
+ try {
7018
+ responseBody = truncate(await response.clone().text());
7019
+ }
7020
+ catch {
7021
+ // A body that cannot be re-read (already disturbed, or an opaque response) is not
7022
+ // worth failing the request over — the status line is still useful on its own.
7023
+ }
7024
+ emit({
7025
+ method,
7026
+ url,
7027
+ path: pathOf(url),
7028
+ statusCode: response.status,
7029
+ durationMs,
7030
+ timestamp: new Date(),
7031
+ requestBody: requestBodyOf(options),
7032
+ responseBody,
7033
+ kind,
7034
+ });
7035
+ return response;
7036
+ }
7037
+ /**
7038
+ * A human-readable description of a thrown fetch failure.
7039
+ *
7040
+ * Never returns undefined for a caught error: a record carrying the transport-failure
7041
+ * sentinel and no reason reads as "no response, and no explanation either", which is worse
7042
+ * than a generic string.
7043
+ */
7044
+ function describeError(error) {
7045
+ if (error instanceof Error) {
7046
+ return error.name ? `${error.name}: ${error.message}` : error.message;
7047
+ }
7048
+ return String(error);
7049
+ }
7050
+
6880
7051
  async function withRetry(url, options, timeout = API_TIMEOUT_LIMIT, retries = API_MAX_CALLS_LIMIT) {
6881
7052
  let retryCount = 0;
6882
7053
  const logRequests = shouldLogHTTPRequests();
@@ -6889,7 +7060,10 @@ async function withRetry(url, options, timeout = API_TIMEOUT_LIMIT, retries = AP
6889
7060
  if (logTraffic && options?.body) {
6890
7061
  logger.debug(`[HTTP] Request body: ${options.body}`);
6891
7062
  }
6892
- const response = await timeoutRequest$1(url, options, timeout);
7063
+ // One record per attempt, emitted before the status branches below: `withRetry`
7064
+ // throws on 409 and on exhausted retries, so recording afterwards would lose the
7065
+ // record for exactly the failures worth seeing (NAM-4606).
7066
+ const response = await observeFetch(url, options, "api", () => timeoutRequest$1(url, options, timeout));
6893
7067
  if (!response.ok) {
6894
7068
  if (logTraffic) {
6895
7069
  const errorBody = await response.clone().text();
@@ -57664,7 +57838,9 @@ async function timeoutRequest(url, timeout) {
57664
57838
  const controller = new AbortController();
57665
57839
  const timeoutId = setTimeout(() => controller.abort(), timeout);
57666
57840
  try {
57667
- return await fetch(url, { signal: controller.signal });
57841
+ // Recorded as `cdn` rather than `api`. Without this the per-paywall body traffic is
57842
+ // invisible to the traffic tab, which is the traffic NAM-4357 is about (NAM-4606).
57843
+ return await observeFetch(url, { method: "GET" }, "cdn", () => fetch(url, { signal: controller.signal }));
57668
57844
  }
57669
57845
  finally {
57670
57846
  clearTimeout(timeoutId);
@@ -65816,4 +65992,4 @@ var internal = /*#__PURE__*/Object.freeze({
65816
65992
  NamiPurchaseManager: NamiPurchaseManager
65817
65993
  });
65818
65994
 
65819
- export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$1 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$1 as NamiCustomerManager, NamiEntitlementManager$1 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$1 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$1 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
65995
+ export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_TRAFFIC_OBSERVER, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEFAULT_JOURNEY_SETTINGS, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DISABLE_ASYNC_LOGIN_LOGOUT, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager$1 as NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager$1 as NamiCustomerManager, NamiEntitlementManager$1 as NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager$1 as NamiFlowManager, NamiFlowStepType, NamiHandoffTag, NamiPaywallAction, NamiPaywallManager$1 as NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiPermissionOutcome, NamiPurchaseManager$1 as NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STARTUP_TELEMETRY, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, TRANSPORT_FAILURE_STATUS, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, NamiProfileManager$1 as _NamiProfileManager, internal as _internal, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, didReceiveResponse, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getJourneySettings, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerAPITrafficObserver, registerPlatformAdapters, registerPurchaseAdapter, resolveLoopSource, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateForm, validateMinSDKVersion, validateTextInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.12-dev.202609241021",
3
+ "version": "3.4.12-dev.202609251500",
4
4
  "description": "Platform-agnostic core for the Nami SDK — business logic, API, types, and state management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",