@chipmobilesdk/rn-ads 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +224 -214
- package/README.md +884 -881
- package/package.json +46 -49
- package/skills/sdk-rn-ads/SKILL.md +273 -274
- package/src/config/resolveAdUnit.ts +85 -85
- package/src/config/types.ts +318 -321
- package/src/config/validate.ts +1 -4
- package/src/core/adOriginatedReturn.ts +134 -134
- package/src/core/adsInstance.ts +1929 -1933
- package/src/core/consentFlow.ts +182 -180
- package/src/core/enablement.ts +76 -76
- package/src/core/fullScreenLock.ts +54 -54
- package/src/core/gates.ts +94 -91
- package/src/core/initMachine.ts +187 -187
- package/src/core/placementMachine.ts +124 -124
- package/src/core/privacyGate.ts +93 -94
- package/src/core/retryPolicy.ts +127 -127
- package/src/core/rewardedSession.ts +84 -84
- package/src/core/timers.ts +216 -208
- package/src/diagnostics/correlation.ts +31 -31
- package/src/diagnostics/errors.ts +230 -234
- package/src/diagnostics/events.ts +238 -232
- package/src/engine/admobEngine.ts +399 -404
- package/src/engine/port.ts +144 -144
- package/src/engine/testAdUnits.ts +61 -61
- package/src/react/AdsBanner.tsx +337 -340
- package/src/react/AdsProvider.tsx +57 -57
- package/src/react/hooks.ts +75 -75
- package/src/testing/index.ts +463 -463
|
@@ -1,234 +1,230 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Normalized error model (FR-053) and redaction (NFR-006).
|
|
3
|
-
*
|
|
4
|
-
* Codes are stable public API: renaming one is a breaking change under LIB-008.
|
|
5
|
-
* They exist so a consuming app branches on `code` and `retryable` rather than
|
|
6
|
-
* string-matching a provider message that may change under it.
|
|
7
|
-
*
|
|
8
|
-
* `cause` is a redacted string, never the provider's error object. A provider
|
|
9
|
-
* object reaching a consumer would be a contract leak (FR-002) and a potential
|
|
10
|
-
* carrier for identifiers the package promises never to emit (FR-021).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { AdFormat } from '../config/types';
|
|
14
|
-
|
|
15
|
-
export type AdsOperation = 'initialize' | 'load' | 'show' | 'configure';
|
|
16
|
-
|
|
17
|
-
export type AdsErrorCode =
|
|
18
|
-
// Configuration — none retryable; the configuration has to change.
|
|
19
|
-
| 'invalid_config'
|
|
20
|
-
| 'duplicate_placement_key'
|
|
21
|
-
| 'missing_ad_unit_id'
|
|
22
|
-
| 'format_mismatch'
|
|
23
|
-
| 'placement_not_found'
|
|
24
|
-
| 'production_in_dev_build'
|
|
25
|
-
| 'timeout_out_of_bounds'
|
|
26
|
-
// Availability — none retryable within this process.
|
|
27
|
-
| 'unsupported_platform'
|
|
28
|
-
| 'provider_missing'
|
|
29
|
-
| 'disabled'
|
|
30
|
-
// The app has not decided whether ads are on. Sibling of `disabled`, and
|
|
31
|
-
// non-retryable for the same reason: calling again changes nothing until the
|
|
32
|
-
// app resolves it (FR-115).
|
|
33
|
-
| 'undecided'
|
|
34
|
-
// Privacy — resolved by consent changing, not by calling again.
|
|
35
|
-
| 'consent_required'
|
|
36
|
-
| 'consent_undetermined'
|
|
37
|
-
| 'requires_reinitialization'
|
|
38
|
-
// The package's own consent flow has not produced permission. Distinct from
|
|
39
|
-
// `consent_required`, which means the app's own gate is closed — a refusal
|
|
40
|
-
// has to name which of the two inputs is blocking (FR-105a).
|
|
41
|
-
| 'consent_flow_required'
|
|
42
|
-
// The consent flow failed. Never retried by the package: FR-135 excludes
|
|
43
|
-
// consent errors, and re-running the flow is the app's call, not a backoff's.
|
|
44
|
-
| 'consent_flow_failed'
|
|
45
|
-
// Runtime.
|
|
46
|
-
| 'no_fill'
|
|
47
|
-
| 'network_error'
|
|
48
|
-
| 'load_timeout'
|
|
49
|
-
| 'show_timeout'
|
|
50
|
-
| 'ad_expired'
|
|
51
|
-
| 'not_ready'
|
|
52
|
-
| 'busy'
|
|
53
|
-
| 'consumed'
|
|
54
|
-
| 'app_inactive'
|
|
55
|
-
// The user came back to the app from an ad, or from a browser an ad opened.
|
|
56
|
-
// Only the package can know this, which is why refusing it is a capability
|
|
57
|
-
// rather than a policy (FR-128). Not retryable: retry covers load, and this
|
|
58
|
-
// is a show refusal that resolves with time rather than with another attempt.
|
|
59
|
-
| 'ad_originated_return'
|
|
60
|
-
| 'provider_error'
|
|
61
|
-
| 'internal_error'
|
|
62
|
-
// Configuration — a retry policy outside its bounds. Rejected rather than
|
|
63
|
-
// clamped, matching `timeout_out_of_bounds`: silently clamping tells the app
|
|
64
|
-
// it configured something it did not.
|
|
65
|
-
| 'retry_out_of_bounds';
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* "Retrying this operation could plausibly succeed" — not "retry immediately".
|
|
69
|
-
*
|
|
70
|
-
* 0.2.0 added an opt-in retry policy, and this set is what it is allowed to act
|
|
71
|
-
* on. Everything outside it is excluded deliberately: configuration and consent
|
|
72
|
-
* errors, an unsupported platform, and a disabled or undecided instance are all
|
|
73
|
-
* states another attempt cannot change, so retrying them would spend a billable
|
|
74
|
-
* request on a certainty (FR-135).
|
|
75
|
-
*/
|
|
76
|
-
const RETRYABLE: ReadonlySet<AdsErrorCode> = new Set<AdsErrorCode>([
|
|
77
|
-
'no_fill',
|
|
78
|
-
'network_error',
|
|
79
|
-
'load_timeout',
|
|
80
|
-
'show_timeout',
|
|
81
|
-
'ad_expired',
|
|
82
|
-
'not_ready',
|
|
83
|
-
'busy',
|
|
84
|
-
'consumed',
|
|
85
|
-
'app_inactive',
|
|
86
|
-
'provider_error',
|
|
87
|
-
]);
|
|
88
|
-
|
|
89
|
-
export interface AdsErrorOptions {
|
|
90
|
-
readonly message?: string;
|
|
91
|
-
readonly operation?: AdsOperation;
|
|
92
|
-
readonly placementKey?: string;
|
|
93
|
-
readonly format?: AdFormat;
|
|
94
|
-
readonly cause?: unknown;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export class AdsError extends Error {
|
|
98
|
-
readonly code: AdsErrorCode;
|
|
99
|
-
readonly retryable: boolean;
|
|
100
|
-
readonly operation?: AdsOperation;
|
|
101
|
-
readonly placementKey?: string;
|
|
102
|
-
readonly format?: AdFormat;
|
|
103
|
-
/** Redacted provider detail. Never a provider object. */
|
|
104
|
-
readonly cause?: string;
|
|
105
|
-
|
|
106
|
-
constructor(code: AdsErrorCode, options: AdsErrorOptions = {}) {
|
|
107
|
-
super(options.message ?? code);
|
|
108
|
-
this.name = 'AdsError';
|
|
109
|
-
this.code = code;
|
|
110
|
-
this.retryable = RETRYABLE.has(code);
|
|
111
|
-
this.operation = options.operation;
|
|
112
|
-
this.placementKey = options.placementKey;
|
|
113
|
-
this.format = options.format;
|
|
114
|
-
this.cause = options.cause === undefined ? undefined : redact(options.cause);
|
|
115
|
-
Object.setPrototypeOf(this, AdsError.prototype);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Configuration faults are thrown rather than returned, because they mean the
|
|
121
|
-
* app is wired wrong and no runtime result could describe a usable outcome.
|
|
122
|
-
* Every other failure in the package is a typed result (FR-004).
|
|
123
|
-
*/
|
|
124
|
-
export class AdsConfigError extends AdsError {
|
|
125
|
-
constructor(code: AdsErrorCode, options: AdsErrorOptions = {}) {
|
|
126
|
-
super(code, { ...options, operation: options.operation ?? 'configure' });
|
|
127
|
-
this.name = 'AdsConfigError';
|
|
128
|
-
Object.setPrototypeOf(this, AdsConfigError.prototype);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function isAdsError(value: unknown): value is AdsError {
|
|
133
|
-
return value instanceof AdsError;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const REDACTED = '[redacted]';
|
|
137
|
-
|
|
138
|
-
/** An AdMob unit or app id. Redacted so a log never carries a live identifier. */
|
|
139
|
-
const AD_UNIT_PATTERN = /ca-app-pub-\d+[~/]\d+/g;
|
|
140
|
-
/** GAID and any other dashed UUID an SDK might put in a message. */
|
|
141
|
-
const UUID_PATTERN =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
-
'
|
|
161
|
-
'
|
|
162
|
-
'
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.replace(
|
|
169
|
-
.replace(
|
|
170
|
-
.replace(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return new AdsError('network_error', { ...options, cause: value });
|
|
232
|
-
}
|
|
233
|
-
return new AdsError('provider_error', { ...options, cause: value });
|
|
234
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Normalized error model (FR-053) and redaction (NFR-006).
|
|
3
|
+
*
|
|
4
|
+
* Codes are stable public API: renaming one is a breaking change under LIB-008.
|
|
5
|
+
* They exist so a consuming app branches on `code` and `retryable` rather than
|
|
6
|
+
* string-matching a provider message that may change under it.
|
|
7
|
+
*
|
|
8
|
+
* `cause` is a redacted string, never the provider's error object. A provider
|
|
9
|
+
* object reaching a consumer would be a contract leak (FR-002) and a potential
|
|
10
|
+
* carrier for identifiers the package promises never to emit (FR-021).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AdFormat } from '../config/types';
|
|
14
|
+
|
|
15
|
+
export type AdsOperation = 'initialize' | 'load' | 'show' | 'configure';
|
|
16
|
+
|
|
17
|
+
export type AdsErrorCode =
|
|
18
|
+
// Configuration — none retryable; the configuration has to change.
|
|
19
|
+
| 'invalid_config'
|
|
20
|
+
| 'duplicate_placement_key'
|
|
21
|
+
| 'missing_ad_unit_id'
|
|
22
|
+
| 'format_mismatch'
|
|
23
|
+
| 'placement_not_found'
|
|
24
|
+
| 'production_in_dev_build'
|
|
25
|
+
| 'timeout_out_of_bounds'
|
|
26
|
+
// Availability — none retryable within this process.
|
|
27
|
+
| 'unsupported_platform'
|
|
28
|
+
| 'provider_missing'
|
|
29
|
+
| 'disabled'
|
|
30
|
+
// The app has not decided whether ads are on. Sibling of `disabled`, and
|
|
31
|
+
// non-retryable for the same reason: calling again changes nothing until the
|
|
32
|
+
// app resolves it (FR-115).
|
|
33
|
+
| 'undecided'
|
|
34
|
+
// Privacy — resolved by consent changing, not by calling again.
|
|
35
|
+
| 'consent_required'
|
|
36
|
+
| 'consent_undetermined'
|
|
37
|
+
| 'requires_reinitialization'
|
|
38
|
+
// The package's own consent flow has not produced permission. Distinct from
|
|
39
|
+
// `consent_required`, which means the app's own gate is closed — a refusal
|
|
40
|
+
// has to name which of the two inputs is blocking (FR-105a).
|
|
41
|
+
| 'consent_flow_required'
|
|
42
|
+
// The consent flow failed. Never retried by the package: FR-135 excludes
|
|
43
|
+
// consent errors, and re-running the flow is the app's call, not a backoff's.
|
|
44
|
+
| 'consent_flow_failed'
|
|
45
|
+
// Runtime.
|
|
46
|
+
| 'no_fill'
|
|
47
|
+
| 'network_error'
|
|
48
|
+
| 'load_timeout'
|
|
49
|
+
| 'show_timeout'
|
|
50
|
+
| 'ad_expired'
|
|
51
|
+
| 'not_ready'
|
|
52
|
+
| 'busy'
|
|
53
|
+
| 'consumed'
|
|
54
|
+
| 'app_inactive'
|
|
55
|
+
// The user came back to the app from an ad, or from a browser an ad opened.
|
|
56
|
+
// Only the package can know this, which is why refusing it is a capability
|
|
57
|
+
// rather than a policy (FR-128). Not retryable: retry covers load, and this
|
|
58
|
+
// is a show refusal that resolves with time rather than with another attempt.
|
|
59
|
+
| 'ad_originated_return'
|
|
60
|
+
| 'provider_error'
|
|
61
|
+
| 'internal_error'
|
|
62
|
+
// Configuration — a retry policy outside its bounds. Rejected rather than
|
|
63
|
+
// clamped, matching `timeout_out_of_bounds`: silently clamping tells the app
|
|
64
|
+
// it configured something it did not.
|
|
65
|
+
| 'retry_out_of_bounds';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* "Retrying this operation could plausibly succeed" — not "retry immediately".
|
|
69
|
+
*
|
|
70
|
+
* 0.2.0 added an opt-in retry policy, and this set is what it is allowed to act
|
|
71
|
+
* on. Everything outside it is excluded deliberately: configuration and consent
|
|
72
|
+
* errors, an unsupported platform, and a disabled or undecided instance are all
|
|
73
|
+
* states another attempt cannot change, so retrying them would spend a billable
|
|
74
|
+
* request on a certainty (FR-135).
|
|
75
|
+
*/
|
|
76
|
+
const RETRYABLE: ReadonlySet<AdsErrorCode> = new Set<AdsErrorCode>([
|
|
77
|
+
'no_fill',
|
|
78
|
+
'network_error',
|
|
79
|
+
'load_timeout',
|
|
80
|
+
'show_timeout',
|
|
81
|
+
'ad_expired',
|
|
82
|
+
'not_ready',
|
|
83
|
+
'busy',
|
|
84
|
+
'consumed',
|
|
85
|
+
'app_inactive',
|
|
86
|
+
'provider_error',
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
export interface AdsErrorOptions {
|
|
90
|
+
readonly message?: string;
|
|
91
|
+
readonly operation?: AdsOperation;
|
|
92
|
+
readonly placementKey?: string;
|
|
93
|
+
readonly format?: AdFormat;
|
|
94
|
+
readonly cause?: unknown;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class AdsError extends Error {
|
|
98
|
+
readonly code: AdsErrorCode;
|
|
99
|
+
readonly retryable: boolean;
|
|
100
|
+
readonly operation?: AdsOperation;
|
|
101
|
+
readonly placementKey?: string;
|
|
102
|
+
readonly format?: AdFormat;
|
|
103
|
+
/** Redacted provider detail. Never a provider object. */
|
|
104
|
+
readonly cause?: string;
|
|
105
|
+
|
|
106
|
+
constructor(code: AdsErrorCode, options: AdsErrorOptions = {}) {
|
|
107
|
+
super(options.message ?? code);
|
|
108
|
+
this.name = 'AdsError';
|
|
109
|
+
this.code = code;
|
|
110
|
+
this.retryable = RETRYABLE.has(code);
|
|
111
|
+
this.operation = options.operation;
|
|
112
|
+
this.placementKey = options.placementKey;
|
|
113
|
+
this.format = options.format;
|
|
114
|
+
this.cause = options.cause === undefined ? undefined : redact(options.cause);
|
|
115
|
+
Object.setPrototypeOf(this, AdsError.prototype);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Configuration faults are thrown rather than returned, because they mean the
|
|
121
|
+
* app is wired wrong and no runtime result could describe a usable outcome.
|
|
122
|
+
* Every other failure in the package is a typed result (FR-004).
|
|
123
|
+
*/
|
|
124
|
+
export class AdsConfigError extends AdsError {
|
|
125
|
+
constructor(code: AdsErrorCode, options: AdsErrorOptions = {}) {
|
|
126
|
+
super(code, { ...options, operation: options.operation ?? 'configure' });
|
|
127
|
+
this.name = 'AdsConfigError';
|
|
128
|
+
Object.setPrototypeOf(this, AdsConfigError.prototype);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isAdsError(value: unknown): value is AdsError {
|
|
133
|
+
return value instanceof AdsError;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const REDACTED = '[redacted]';
|
|
137
|
+
|
|
138
|
+
/** An AdMob unit or app id. Redacted so a log never carries a live identifier. */
|
|
139
|
+
const AD_UNIT_PATTERN = /ca-app-pub-\d+[~/]\d+/g;
|
|
140
|
+
/** GAID and any other dashed UUID an SDK might put in a message. */
|
|
141
|
+
const UUID_PATTERN = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g;
|
|
142
|
+
/** Base64-ish runs long enough to be an encoded payload such as a consent string. */
|
|
143
|
+
const PAYLOAD_PATTERN = /[A-Za-z0-9+/=_-]{40,}/g;
|
|
144
|
+
/** Anything path- or URL-like, which can carry query parameters with identifiers. */
|
|
145
|
+
const URL_PATTERN = /[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s"']+/g;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Keys dropped wholesale rather than pattern-matched, because their values are
|
|
149
|
+
* sensitive regardless of shape — a bare advertising id has no separator for a
|
|
150
|
+
* pattern to catch.
|
|
151
|
+
*/
|
|
152
|
+
const SENSITIVE_KEYS = new Set([
|
|
153
|
+
'adid',
|
|
154
|
+
'adUnitId',
|
|
155
|
+
'advertisingId',
|
|
156
|
+
'consent',
|
|
157
|
+
'consentString',
|
|
158
|
+
'gaid',
|
|
159
|
+
'idfa',
|
|
160
|
+
'tcString',
|
|
161
|
+
'token',
|
|
162
|
+
'userId',
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
function redactString(value: string): string {
|
|
166
|
+
return value
|
|
167
|
+
.replace(URL_PATTERN, REDACTED)
|
|
168
|
+
.replace(AD_UNIT_PATTERN, REDACTED)
|
|
169
|
+
.replace(UUID_PATTERN, REDACTED)
|
|
170
|
+
.replace(PAYLOAD_PATTERN, REDACTED);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Reduces an arbitrary provider value to a safe string (FR-053, NFR-006).
|
|
175
|
+
*
|
|
176
|
+
* Returns a string rather than a structure on purpose: a redacted object still
|
|
177
|
+
* hands the consumer a reference into provider-owned memory, and the contract
|
|
178
|
+
* promises no provider object crosses the boundary.
|
|
179
|
+
*/
|
|
180
|
+
export function redact(value: unknown, depth = 0): string {
|
|
181
|
+
if (value === null || value === undefined) {
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
if (typeof value === 'string') {
|
|
185
|
+
return redactString(value);
|
|
186
|
+
}
|
|
187
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
188
|
+
return String(value);
|
|
189
|
+
}
|
|
190
|
+
if (value instanceof Error) {
|
|
191
|
+
return redactString(value.message);
|
|
192
|
+
}
|
|
193
|
+
if (depth >= 2) {
|
|
194
|
+
return REDACTED;
|
|
195
|
+
}
|
|
196
|
+
if (Array.isArray(value)) {
|
|
197
|
+
return value.map(item => redact(item, depth + 1)).join(', ');
|
|
198
|
+
}
|
|
199
|
+
if (typeof value === 'object') {
|
|
200
|
+
const parts: string[] = [];
|
|
201
|
+
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
202
|
+
if (SENSITIVE_KEYS.has(key)) {
|
|
203
|
+
parts.push(`${key}=${REDACTED}`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
parts.push(`${key}=${redact(entry, depth + 1)}`);
|
|
207
|
+
}
|
|
208
|
+
return parts.join(' ');
|
|
209
|
+
}
|
|
210
|
+
return REDACTED;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Maps a provider failure onto the normalized taxonomy. Provider codes vary by
|
|
215
|
+
* SDK version, so the match is on substrings that have been stable across
|
|
216
|
+
* releases, and anything unrecognized becomes a retryable `provider_error`
|
|
217
|
+
* rather than being silently reshaped into a code that means something specific.
|
|
218
|
+
*/
|
|
219
|
+
export function normalizeProviderError(value: unknown, options: AdsErrorOptions = {}): AdsError {
|
|
220
|
+
const text = value instanceof Error ? value.message : String(value ?? '');
|
|
221
|
+
const lowered = text.toLowerCase();
|
|
222
|
+
|
|
223
|
+
if (lowered.includes('no-fill') || lowered.includes('no fill')) {
|
|
224
|
+
return new AdsError('no_fill', { ...options, cause: value });
|
|
225
|
+
}
|
|
226
|
+
if (lowered.includes('network')) {
|
|
227
|
+
return new AdsError('network_error', { ...options, cause: value });
|
|
228
|
+
}
|
|
229
|
+
return new AdsError('provider_error', { ...options, cause: value });
|
|
230
|
+
}
|