@chipmobilesdk/rn-ads 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -1
- package/README.md +881 -771
- package/package.json +49 -49
- package/skills/sdk-rn-ads/SKILL.md +274 -240
- package/src/config/types.ts +18 -0
- package/src/core/adsInstance.ts +1933 -1670
- package/src/core/consentFlow.ts +14 -1
- package/src/core/initMachine.ts +13 -0
- package/src/core/placementMachine.ts +12 -2
- package/src/core/privacyGate.ts +10 -1
- package/src/engine/admobEngine.ts +33 -2
- package/src/engine/port.ts +30 -2
- package/src/index.ts +103 -101
- package/src/react/AdsBanner.tsx +340 -337
- package/src/testing/index.ts +106 -4
package/src/testing/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
EngineConsentInfo,
|
|
27
27
|
EngineConsentOptions,
|
|
28
28
|
EnginePrivacy,
|
|
29
|
+
EngineRequestOptions,
|
|
29
30
|
} from '../engine/port';
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -97,9 +98,27 @@ export function createTestClock(startMs = 1_000_000): TestClock {
|
|
|
97
98
|
export type FakeConsentCall =
|
|
98
99
|
| { readonly kind: 'requestInfoUpdate'; readonly options: EngineConsentOptions }
|
|
99
100
|
| { readonly kind: 'showFormIfRequired' }
|
|
101
|
+
| { readonly kind: 'showPrivacyOptionsForm' }
|
|
100
102
|
| { readonly kind: 'getInfo' }
|
|
101
103
|
| { readonly kind: 'reset' };
|
|
102
104
|
|
|
105
|
+
/**
|
|
106
|
+
* A privacy options form the test is holding open (0.4.0).
|
|
107
|
+
*
|
|
108
|
+
* Settle it whenever the test chooses, which is what lets `busy`, a second
|
|
109
|
+
* call, a consent update, and `dispose()` all be exercised *while* the form is
|
|
110
|
+
* on screen — in any order, rather than only the one a resolved promise allows.
|
|
111
|
+
*/
|
|
112
|
+
export interface HeldPrivacyOptionsForm {
|
|
113
|
+
/**
|
|
114
|
+
* The user closes the form. `info` is what the provider reports afterwards,
|
|
115
|
+
* merged over the current information; omit it for a user who changed nothing.
|
|
116
|
+
*/
|
|
117
|
+
close(info?: Partial<EngineConsentInfo>): void;
|
|
118
|
+
/** The form fails while open. */
|
|
119
|
+
fail(cause?: unknown): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
export type FakeEngineCall =
|
|
104
123
|
| { readonly kind: 'initialize'; readonly privacy: EnginePrivacy }
|
|
105
124
|
| {
|
|
@@ -107,6 +126,8 @@ export type FakeEngineCall =
|
|
|
107
126
|
readonly format: FullScreenFormat;
|
|
108
127
|
readonly adUnitId: string;
|
|
109
128
|
readonly handleId: string;
|
|
129
|
+
/** Per-request privacy as the package sent it (FR-017's personalization half). */
|
|
130
|
+
readonly request?: EngineRequestOptions;
|
|
110
131
|
}
|
|
111
132
|
| { readonly kind: 'show'; readonly handleId: string }
|
|
112
133
|
| { readonly kind: 'dispose'; readonly handleId: string };
|
|
@@ -133,6 +154,7 @@ const DEFAULT_CONSENT: EngineConsentInfo = {
|
|
|
133
154
|
status: 'not_required',
|
|
134
155
|
canRequestAds: true,
|
|
135
156
|
isFormAvailable: false,
|
|
157
|
+
privacyOptions: 'not_required',
|
|
136
158
|
};
|
|
137
159
|
|
|
138
160
|
export interface FakeAdsEngine extends AdsEngine {
|
|
@@ -166,6 +188,19 @@ export interface FakeAdsEngine extends AdsEngine {
|
|
|
166
188
|
failConsentRequest(cause?: unknown): void;
|
|
167
189
|
/** Makes `showFormIfRequired` reject — dismissal is not consent (FR-106). */
|
|
168
190
|
failConsentForm(cause?: unknown): void;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* What the privacy options form reports when the user closes it, merged over
|
|
194
|
+
* the current information. Unset, the form closes with nothing changed — the
|
|
195
|
+
* same as a user who opens it and backs out.
|
|
196
|
+
*/
|
|
197
|
+
setPrivacyOptionsFormResult(info: Partial<EngineConsentInfo>): void;
|
|
198
|
+
/** Makes `showPrivacyOptionsForm` reject — a failure is never consent (FR-106). */
|
|
199
|
+
failPrivacyOptionsForm(cause?: unknown): void;
|
|
200
|
+
/** Holds the next privacy options form open until the test settles it. */
|
|
201
|
+
holdPrivacyOptionsForm(): HeldPrivacyOptionsForm;
|
|
202
|
+
/** Makes `getInfo` reject, for the case where even the re-read after a failed form fails. */
|
|
203
|
+
failConsentInfoRead(cause?: unknown): void;
|
|
169
204
|
}
|
|
170
205
|
|
|
171
206
|
export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEngine {
|
|
@@ -175,6 +210,11 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
|
|
|
175
210
|
let consentInfo: EngineConsentInfo = { ...DEFAULT_CONSENT, ...options.consentInfo };
|
|
176
211
|
let consentRequestFailure: unknown;
|
|
177
212
|
let consentFormFailure: unknown;
|
|
213
|
+
let privacyFormFailure: unknown;
|
|
214
|
+
let privacyFormResult: Partial<EngineConsentInfo> = {};
|
|
215
|
+
let consentReadFailure: unknown;
|
|
216
|
+
/** Set by `holdPrivacyOptionsForm`; consumed by the next form. */
|
|
217
|
+
let heldForm: Promise<Partial<EngineConsentInfo>> | null = null;
|
|
178
218
|
|
|
179
219
|
const consent: ConsentPort = {
|
|
180
220
|
async requestInfoUpdate(consentOptions) {
|
|
@@ -192,18 +232,49 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
|
|
|
192
232
|
// A presented form that the user completes moves the status on. The
|
|
193
233
|
// fake only does this when a form was actually required, mirroring the
|
|
194
234
|
// provider: showing nothing cannot change an answer.
|
|
235
|
+
// A region that required a form is one that requires a way back into
|
|
236
|
+
// it, which is what the provider reports once consent is obtained.
|
|
195
237
|
if (consentInfo.status === 'required' && consentInfo.isFormAvailable) {
|
|
196
|
-
consentInfo = {
|
|
238
|
+
consentInfo = {
|
|
239
|
+
status: 'obtained',
|
|
240
|
+
canRequestAds: true,
|
|
241
|
+
isFormAvailable: false,
|
|
242
|
+
privacyOptions: 'required',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return consentInfo;
|
|
246
|
+
},
|
|
247
|
+
async showPrivacyOptionsForm() {
|
|
248
|
+
consentCalls.push({ kind: 'showPrivacyOptionsForm' });
|
|
249
|
+
const held = heldForm;
|
|
250
|
+
heldForm = null;
|
|
251
|
+
if (held) {
|
|
252
|
+
// Rejects if the test calls `fail`, which is exactly the provider's
|
|
253
|
+
// failure path; resolves with what the user left behind otherwise.
|
|
254
|
+
consentInfo = { ...consentInfo, ...(await held) };
|
|
255
|
+
return consentInfo;
|
|
197
256
|
}
|
|
257
|
+
if (privacyFormFailure !== undefined) {
|
|
258
|
+
throw privacyFormFailure;
|
|
259
|
+
}
|
|
260
|
+
consentInfo = { ...consentInfo, ...privacyFormResult };
|
|
198
261
|
return consentInfo;
|
|
199
262
|
},
|
|
200
263
|
async getInfo() {
|
|
201
264
|
consentCalls.push({ kind: 'getInfo' });
|
|
265
|
+
if (consentReadFailure !== undefined) {
|
|
266
|
+
throw consentReadFailure;
|
|
267
|
+
}
|
|
202
268
|
return consentInfo;
|
|
203
269
|
},
|
|
204
270
|
reset() {
|
|
205
271
|
consentCalls.push({ kind: 'reset' });
|
|
206
|
-
consentInfo = {
|
|
272
|
+
consentInfo = {
|
|
273
|
+
status: 'unknown',
|
|
274
|
+
canRequestAds: false,
|
|
275
|
+
isFormAvailable: true,
|
|
276
|
+
privacyOptions: 'unknown',
|
|
277
|
+
};
|
|
207
278
|
},
|
|
208
279
|
};
|
|
209
280
|
const listeners = new Map<string, Set<(event: EngineAdEvent) => void>>();
|
|
@@ -238,6 +309,37 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
|
|
|
238
309
|
consentFormFailure = cause;
|
|
239
310
|
},
|
|
240
311
|
|
|
312
|
+
setPrivacyOptionsFormResult(info) {
|
|
313
|
+
privacyFormResult = info;
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
failPrivacyOptionsForm(cause = new Error('privacy options form failed')) {
|
|
317
|
+
privacyFormFailure = cause;
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
holdPrivacyOptionsForm() {
|
|
321
|
+
let settle!: {
|
|
322
|
+
resolve: (info: Partial<EngineConsentInfo>) => void;
|
|
323
|
+
reject: (cause: unknown) => void;
|
|
324
|
+
};
|
|
325
|
+
const promise = new Promise<Partial<EngineConsentInfo>>((resolve, reject) => {
|
|
326
|
+
settle = { resolve, reject };
|
|
327
|
+
});
|
|
328
|
+
heldForm = promise;
|
|
329
|
+
return {
|
|
330
|
+
close(info = {}) {
|
|
331
|
+
settle.resolve(info);
|
|
332
|
+
},
|
|
333
|
+
fail(cause = new Error('privacy options form failed')) {
|
|
334
|
+
settle.reject(cause);
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
failConsentInfoRead(cause = new Error('consent information read failed')) {
|
|
340
|
+
consentReadFailure = cause;
|
|
341
|
+
},
|
|
342
|
+
|
|
241
343
|
isAvailable() {
|
|
242
344
|
return options.available !== false;
|
|
243
345
|
},
|
|
@@ -249,10 +351,10 @@ export function createFakeAdsEngine(options: FakeEngineOptions = {}): FakeAdsEng
|
|
|
249
351
|
}
|
|
250
352
|
},
|
|
251
353
|
|
|
252
|
-
async createFullScreen(format, adUnitId) {
|
|
354
|
+
async createFullScreen(format, adUnitId, request) {
|
|
253
355
|
counter += 1;
|
|
254
356
|
const id = `fake-${counter}`;
|
|
255
|
-
record({ kind: 'createFullScreen', format, adUnitId, handleId: id });
|
|
357
|
+
record({ kind: 'createFullScreen', format, adUnitId, handleId: id, request });
|
|
256
358
|
live.add(id);
|
|
257
359
|
// A real provider delivers `loaded` to a listener attached after the
|
|
258
360
|
// ad object exists, so the callback is queued here and flushed when
|