@gabagool-sdk/react-native 0.0.0-stage → 0.1.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.
@@ -0,0 +1,62 @@
1
+ import { bannerFailureStatus } from './errors.js';
2
+ /** Máximo de requests por montagem: primário + `<slot>_fallback` (nunca volta ao primário, como a Dart). */
3
+ export const MAX_BANNER_ATTEMPTS = 2;
4
+ /** Lógica do Banner sem React: primário -> fallback, banner_load real e nenhuma rejeição solta. */
5
+ export class BannerFlow {
6
+ ports;
7
+ screen;
8
+ slot;
9
+ hooks;
10
+ generation = 0;
11
+ attempts = 0;
12
+ handle = null;
13
+ constructor(ports, screen, slot, hooks) {
14
+ this.ports = ports;
15
+ this.screen = screen;
16
+ this.slot = slot;
17
+ this.hooks = hooks;
18
+ }
19
+ start() {
20
+ this.generation++;
21
+ this.attempts = 0;
22
+ this.request();
23
+ }
24
+ stop() { this.generation++; }
25
+ loaded() {
26
+ if (!this.handle)
27
+ return;
28
+ this.safe(() => this.ports.trackLoad(this.handle.adUnit, 'fill'));
29
+ this.hooks.onLoaded?.();
30
+ }
31
+ failed(error) {
32
+ const failed = this.handle;
33
+ if (!failed)
34
+ return;
35
+ this.safe(() => this.ports.trackLoad(failed.adUnit, bannerFailureStatus(error)));
36
+ this.hooks.onFailedToLoad?.(error);
37
+ this.attempts++;
38
+ if (this.attempts >= MAX_BANNER_ATTEMPTS) {
39
+ this.set(null);
40
+ return;
41
+ }
42
+ this.safe(() => this.ports.markFailed(failed));
43
+ this.request();
44
+ }
45
+ request() {
46
+ const generation = this.generation;
47
+ void this.ports.ready()
48
+ .then(() => this.ports.bannerFor(this.screen, this.slot))
49
+ .then((next) => { if (generation === this.generation)
50
+ this.set(next); })
51
+ .catch(() => { if (generation === this.generation)
52
+ this.set(null); });
53
+ }
54
+ set(handle) {
55
+ this.handle = handle;
56
+ this.hooks.setHandle(handle);
57
+ }
58
+ safe(fn) { try {
59
+ fn();
60
+ }
61
+ catch { /* telemetria nunca derruba o banner */ } }
62
+ }
@@ -0,0 +1,9 @@
1
+ export type LoadErrorLike = {
2
+ code?: string;
3
+ reason?: string;
4
+ };
5
+ /** Código numérico do erro de load do Invertase (3 = no fill); undefined se não reconhecido. */
6
+ export declare const numericCode: (error: LoadErrorLike) => number | undefined;
7
+ /** Status de `banner_load` para uma falha do componente. */
8
+ export declare const bannerFailureStatus: (error: LoadErrorLike) => "no_fill" | "error";
9
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D,gGAAgG;AAChG,eAAO,MAAM,WAAW,GAAI,OAAO,aAAa,KAAG,MAAM,GAAG,SAI3D,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,GAAI,OAAO,aAAa,KAAG,SAAS,GAAG,OACvB,CAAC"}
package/dist/errors.js ADDED
@@ -0,0 +1,9 @@
1
+ /** Código numérico do erro de load do Invertase (3 = no fill); undefined se não reconhecido. */
2
+ export const numericCode = (error) => {
3
+ if (error.reason === 'no-fill' || error.reason === 'mediation-no-fill')
4
+ return 3;
5
+ const match = error.code?.match(/(?:^|\/)(\d+)$/);
6
+ return match ? Number(match[1]) : undefined;
7
+ };
8
+ /** Status de `banner_load` para uma falha do componente. */
9
+ export const bannerFailureStatus = (error) => numericCode(error) === 3 ? 'no_fill' : 'error';
@@ -0,0 +1,53 @@
1
+ import { type AdHandle, type AdNetworkAdapter, type BootAdResult, type FetchLike, type Gabagool, type GateResult, type InterstitialShowResult, type KVStorage, type RewardedResult, type TelemetryTransport, type WrapperConfig } from '@gabagool-sdk/core';
2
+ import { ReactNativeGmaAdapter } from './adapter.js';
3
+ import { type InstallInfoProvider } from './installInfo.js';
4
+ export interface GabagoolInitOptions {
5
+ adapter?: AdNetworkAdapter;
6
+ storage?: KVStorage;
7
+ fetch?: FetchLike;
8
+ transport?: TelemetryTransport;
9
+ requestConsent?: boolean;
10
+ preload?: boolean;
11
+ installInfoProvider?: InstallInfoProvider;
12
+ installReferrer?: string;
13
+ installBeginTimestampSeconds?: number;
14
+ installTimeProvider?: () => Promise<number | null | undefined> | number | null | undefined;
15
+ deepLink?: string;
16
+ override?: Record<string, string>;
17
+ fallback?: WrapperConfig;
18
+ appVersion?: string;
19
+ deviceModel?: string;
20
+ advertisingId?: string;
21
+ debug?: boolean;
22
+ context?: Record<string, unknown>;
23
+ }
24
+ /** Fachada singleton equivalente à fachada Dart. */
25
+ export declare class GabagoolAds {
26
+ private static instance;
27
+ private static lifecycle;
28
+ private static gmaAdapter;
29
+ private static readyState;
30
+ private static newReady;
31
+ /** Resolve quando o `init` terminou (Banner pode montar antes; ver BannerFlow). Nunca rejeita. */
32
+ static whenReady(): Promise<void>;
33
+ private static resetReady;
34
+ static get core(): Gabagool;
35
+ static init(apiKey: string, baseUrl: string, appId: string, options?: GabagoolInitOptions): Promise<void>;
36
+ static showInterstitial(screen?: string): Promise<InterstitialShowResult>;
37
+ static showRewarded(screen?: string): Promise<RewardedResult>;
38
+ static gateUnlock(screen?: string): Promise<GateResult>;
39
+ static bannerFor(screen: string, slot?: string): Promise<AdHandle | null>;
40
+ static showBootAd(): Promise<BootAdResult>;
41
+ static onAppBackground(): Promise<void>;
42
+ static onAppForeground(): Promise<boolean>;
43
+ static onBackground(): Promise<void>;
44
+ static onForeground(): Promise<boolean>;
45
+ static refresh(): Promise<void>;
46
+ static setSegmentOverride(slug: string): Promise<boolean>;
47
+ static identify(userId: string, traits?: Record<string, unknown>): Promise<void>;
48
+ static track(name: string, props?: Record<string, unknown>): Promise<void>;
49
+ static dispose(): void;
50
+ /** Uso interno do componente Banner para mapear falha real do componente para o fallback do core. */
51
+ static nativeAdapter(): ReactNativeGmaAdapter | null;
52
+ }
53
+ //# sourceMappingURL=facade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"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"}
package/dist/facade.js ADDED
@@ -0,0 +1,104 @@
1
+ import { AdsConsent } from 'react-native-google-mobile-ads';
2
+ import { Dimensions, Platform } from 'react-native';
3
+ import { createGabagool, fallbackConfig, } from '@gabagool-sdk/core';
4
+ import { ReactNativeGmaAdapter } from './adapter.js';
5
+ import { ReactNativeInstallInfoProvider } from './installInfo.js';
6
+ import { AppStateLifecycle } from './lifecycle.js';
7
+ import { AsyncStorageAdapter } from './storage.js';
8
+ import { createReactNativeTransport, reactNativeFetch } from './transport.js';
9
+ /** Fachada singleton equivalente à fachada Dart. */
10
+ export class GabagoolAds {
11
+ static instance = createGabagool();
12
+ static lifecycle = null;
13
+ static gmaAdapter = null;
14
+ static readyState = GabagoolAds.newReady();
15
+ static newReady() {
16
+ let resolve;
17
+ const promise = new Promise((r) => { resolve = r; });
18
+ return { done: false, promise, resolve };
19
+ }
20
+ /** Resolve quando o `init` terminou (Banner pode montar antes; ver BannerFlow). Nunca rejeita. */
21
+ static whenReady() { return this.readyState.promise; }
22
+ static resetReady() {
23
+ if (this.readyState.done)
24
+ this.readyState = this.newReady();
25
+ }
26
+ static get core() { return this.instance; }
27
+ static async init(apiKey, baseUrl, appId, options = {}) {
28
+ this.lifecycle?.stop();
29
+ this.instance.dispose();
30
+ this.resetReady();
31
+ this.instance = createGabagool();
32
+ const adapter = options.adapter ?? new ReactNativeGmaAdapter();
33
+ this.gmaAdapter = adapter instanceof ReactNativeGmaAdapter ? adapter : null;
34
+ if (this.gmaAdapter)
35
+ await this.gmaAdapter.initialize();
36
+ if (options.requestConsent) {
37
+ // UMP é deliberadamente assíncrono: falha/formulário não bloqueia o fluxo de anúncios.
38
+ void AdsConsent.requestInfoUpdate()
39
+ .then(() => AdsConsent.loadAndShowConsentFormIfRequired())
40
+ .catch(() => undefined);
41
+ }
42
+ const install = await (options.installInfoProvider
43
+ ?? new ReactNativeInstallInfoProvider(options.installTimeProvider)).get().catch(() => ({}));
44
+ const screen = Dimensions.get('screen');
45
+ const platform = Platform.OS === 'ios' ? 'ios' : 'android';
46
+ const fetchPort = options.fetch ?? reactNativeFetch;
47
+ await this.instance.init({
48
+ baseUrl,
49
+ apiKey,
50
+ appId,
51
+ adapter,
52
+ platform,
53
+ storage: options.storage ?? new AsyncStorageAdapter(),
54
+ fetch: fetchPort,
55
+ transport: options.transport ?? createReactNativeTransport(baseUrl, apiKey),
56
+ installReferrer: options.installReferrer ?? install.installReferrer,
57
+ installBeginTimestampSeconds: options.installBeginTimestampSeconds ?? install.installBeginTimestampSeconds,
58
+ installTimeProvider: options.installTimeProvider
59
+ ?? (install.installTimeMs === undefined ? undefined : () => install.installTimeMs),
60
+ deepLink: options.deepLink ?? install.deepLink,
61
+ ...(options.override ? { override: options.override } : {}),
62
+ fallbackConfig: options.fallback ?? fallbackConfig,
63
+ context: {
64
+ locale: Intl.DateTimeFormat().resolvedOptions().locale,
65
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
66
+ screen_width: Math.round(screen.width * screen.scale),
67
+ screen_height: Math.round(screen.height * screen.scale),
68
+ ...(options.appVersion ? { app_version: options.appVersion } : {}),
69
+ ...(options.deviceModel ? { device_model: options.deviceModel } : {}),
70
+ ...(options.advertisingId ? { advertising_id: options.advertisingId } : {}),
71
+ ...(options.context ?? {}),
72
+ },
73
+ debug: options.debug === true,
74
+ });
75
+ this.lifecycle = new AppStateLifecycle(this.instance);
76
+ this.lifecycle.start();
77
+ this.readyState.done = true;
78
+ this.readyState.resolve();
79
+ if (options.preload !== false)
80
+ void this.instance.startAdPreload().catch(() => undefined);
81
+ }
82
+ static showInterstitial(screen) { return this.instance.showInterstitial(screen); }
83
+ static showRewarded(screen) { return this.instance.showRewarded(screen); }
84
+ static gateUnlock(screen) { return this.instance.showGate(screen); }
85
+ static bannerFor(screen, slot = 'banner') { return this.instance.bannerFor(screen, slot); }
86
+ static showBootAd() { return this.instance.loadAndShowBootAd(); }
87
+ static onAppBackground() { return this.instance.onBackground(); }
88
+ static onAppForeground() { return this.instance.onForeground(); }
89
+ static onBackground() { return this.onAppBackground(); }
90
+ static onForeground() { return this.onAppForeground(); }
91
+ static refresh() { return this.instance.refresh(); }
92
+ static setSegmentOverride(slug) { return this.instance.setSegmentOverride(slug); }
93
+ static identify(userId, traits) { return this.instance.identify(userId, traits); }
94
+ static track(name, props) { return this.instance.track(name, props); }
95
+ static dispose() {
96
+ this.lifecycle?.stop();
97
+ this.lifecycle = null;
98
+ this.instance.dispose();
99
+ this.gmaAdapter = null;
100
+ this.resetReady();
101
+ }
102
+ /** Uso interno do componente Banner para mapear falha real do componente para o fallback do core. */
103
+ static nativeAdapter() { return this.gmaAdapter; }
104
+ }
@@ -0,0 +1,9 @@
1
+ export { ReactNativeGmaAdapter } from './adapter.js';
2
+ export { AsyncStorageAdapter } from './storage.js';
3
+ export { reactNativeFetch, createReactNativeTransport } from './transport.js';
4
+ export { AppStateLifecycle, type LifecycleTarget } from './lifecycle.js';
5
+ export { ReactNativeInstallInfoProvider, type InstallInfo, type InstallInfoProvider } from './installInfo.js';
6
+ export { GabagoolAds, type GabagoolInitOptions } from './facade.js';
7
+ export { Banner, type BannerProps } from './Banner.js';
8
+ export * from '@gabagool-sdk/core';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,8BAA8B,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACvD,cAAc,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { ReactNativeGmaAdapter } from './adapter.js';
2
+ export { AsyncStorageAdapter } from './storage.js';
3
+ export { reactNativeFetch, createReactNativeTransport } from './transport.js';
4
+ export { AppStateLifecycle } from './lifecycle.js';
5
+ export { ReactNativeInstallInfoProvider } from './installInfo.js';
6
+ export { GabagoolAds } from './facade.js';
7
+ export { Banner } from './Banner.js';
8
+ export * from '@gabagool-sdk/core';
@@ -0,0 +1,17 @@
1
+ export interface InstallInfo {
2
+ installReferrer?: string;
3
+ installBeginTimestampSeconds?: number;
4
+ installTimeMs?: number;
5
+ deepLink?: string;
6
+ }
7
+ export interface InstallInfoProvider {
8
+ get(): Promise<InstallInfo>;
9
+ }
10
+ /** Provider injetável nos testes/iOS; falhas do módulo nativo viram campos ausentes. */
11
+ export declare class ReactNativeInstallInfoProvider implements InstallInfoProvider {
12
+ private readonly installTimeProvider?;
13
+ constructor(installTimeProvider?: (() => Promise<number | null | undefined> | number | null | undefined) | undefined);
14
+ get(): Promise<InstallInfo>;
15
+ private readReferrer;
16
+ }
17
+ //# sourceMappingURL=installInfo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installInfo.d.ts","sourceRoot":"","sources":["../src/installInfo.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAAG,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CAAE;AAErE,wFAAwF;AACxF,qBAAa,8BAA+B,YAAW,mBAAmB;IAC5D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;gBAApB,mBAAmB,CAAC,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,aAAA;IAEjH,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC;IAWjC,OAAO,CAAC,YAAY;CAerB"}
@@ -0,0 +1,38 @@
1
+ import { Linking, Platform } from 'react-native';
2
+ import { PlayInstallReferrer } from 'react-native-play-install-referrer';
3
+ /** Provider injetável nos testes/iOS; falhas do módulo nativo viram campos ausentes. */
4
+ export class ReactNativeInstallInfoProvider {
5
+ installTimeProvider;
6
+ constructor(installTimeProvider) {
7
+ this.installTimeProvider = installTimeProvider;
8
+ }
9
+ async get() {
10
+ const deepLink = await Linking.getInitialURL().catch(() => null);
11
+ const installTimeMs = await Promise.resolve(this.installTimeProvider?.()).catch(() => null);
12
+ const referrer = Platform.OS === 'android' ? await this.readReferrer() : {};
13
+ return {
14
+ ...referrer,
15
+ ...(typeof installTimeMs === 'number' && installTimeMs > 0 ? { installTimeMs } : {}),
16
+ ...(deepLink ? { deepLink } : {}),
17
+ };
18
+ }
19
+ readReferrer() {
20
+ return new Promise((resolve) => {
21
+ try {
22
+ PlayInstallReferrer.getInstallReferrerInfo((info, error) => {
23
+ if (error || !info)
24
+ return resolve({});
25
+ resolve({
26
+ ...(info.installReferrer ? { installReferrer: info.installReferrer } : {}),
27
+ ...(info.installBeginTimestampSeconds > 0
28
+ ? { installBeginTimestampSeconds: info.installBeginTimestampSeconds }
29
+ : {}),
30
+ });
31
+ });
32
+ }
33
+ catch {
34
+ resolve({});
35
+ }
36
+ });
37
+ }
38
+ }
@@ -0,0 +1,16 @@
1
+ export interface LifecycleTarget {
2
+ onBackground(): Promise<void>;
3
+ onForeground(): Promise<boolean>;
4
+ }
5
+ /** Traduz AppState para o lifecycle puro do core e serializa callbacks. */
6
+ export declare class AppStateLifecycle {
7
+ private readonly target;
8
+ private state;
9
+ private subscription;
10
+ private pending;
11
+ constructor(target: LifecycleTarget);
12
+ start(): void;
13
+ stop(): void;
14
+ private readonly onChange;
15
+ }
16
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAClC;AAED,2EAA2E;AAC3E,qBAAa,iBAAiB;IAKhB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,OAAO,CAAuC;gBAEzB,MAAM,EAAE,eAAe;IAEpD,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI;IAKZ,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAQvB;CACH"}
@@ -0,0 +1,30 @@
1
+ import { AppState } from 'react-native';
2
+ /** Traduz AppState para o lifecycle puro do core e serializa callbacks. */
3
+ export class AppStateLifecycle {
4
+ target;
5
+ state = AppState.currentState;
6
+ subscription = null;
7
+ pending = Promise.resolve();
8
+ constructor(target) {
9
+ this.target = target;
10
+ }
11
+ start() {
12
+ if (this.subscription)
13
+ return;
14
+ this.subscription = AppState.addEventListener('change', this.onChange);
15
+ }
16
+ stop() {
17
+ this.subscription?.remove();
18
+ this.subscription = null;
19
+ }
20
+ onChange = (next) => {
21
+ const previous = this.state;
22
+ this.state = next;
23
+ if (previous === 'active' && next !== 'active') {
24
+ this.pending = this.pending.then(() => this.target.onBackground()).catch(() => undefined);
25
+ }
26
+ else if (previous !== 'active' && next === 'active') {
27
+ this.pending = this.pending.then(() => this.target.onForeground()).catch(() => undefined);
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,8 @@
1
+ import type { KVStorage } from '@gabagool-sdk/core';
2
+ /** Porta KV concreta; o namespace `@gabagool/` continua sendo aplicado pelo core. */
3
+ export declare class AsyncStorageAdapter implements KVStorage {
4
+ get(key: string): Promise<string | null>;
5
+ set(key: string, value: string): Promise<void>;
6
+ remove(key: string): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,qFAAqF;AACrF,qBAAa,mBAAoB,YAAW,SAAS;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAGxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}
@@ -0,0 +1,24 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ /** Porta KV concreta; o namespace `@gabagool/` continua sendo aplicado pelo core. */
3
+ export class AsyncStorageAdapter {
4
+ async get(key) {
5
+ try {
6
+ return await AsyncStorage.getItem(key);
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ }
12
+ async set(key, value) {
13
+ try {
14
+ await AsyncStorage.setItem(key, value);
15
+ }
16
+ catch { /* persistência nunca derruba o app */ }
17
+ }
18
+ async remove(key) {
19
+ try {
20
+ await AsyncStorage.removeItem(key);
21
+ }
22
+ catch { /* persistência nunca derruba o app */ }
23
+ }
24
+ }
@@ -0,0 +1,5 @@
1
+ import { type FetchLike, type TelemetryTransport } from '@gabagool-sdk/core';
2
+ /** Fetch do React Native com a mesma restrição de transporte do porte Dart. */
3
+ export declare const reactNativeFetch: FetchLike;
4
+ export declare const createReactNativeTransport: (baseUrl: string, apiKey: string) => TelemetryTransport;
5
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAS5F,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,EAAE,SAG9B,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,SAAS,MAAM,EAAE,QAAQ,MAAM,KAAG,kBAChB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { httpTransport } from '@gabagool-sdk/core';
2
+ function allowed(url) {
3
+ try {
4
+ const u = new URL(url);
5
+ return u.protocol === 'https:' || (u.protocol === 'http:' && ['localhost', '127.0.0.1', '10.0.2.2'].includes(u.hostname));
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ /** Fetch do React Native com a mesma restrição de transporte do porte Dart. */
12
+ export const reactNativeFetch = async (url, init) => {
13
+ if (!allowed(url))
14
+ throw new Error('insecure_transport');
15
+ return fetch(url, init);
16
+ };
17
+ export const createReactNativeTransport = (baseUrl, apiKey) => httpTransport({ baseUrl, apiKey, fetch: reactNativeFetch });
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@gabagool-sdk/react-native",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "React Native SDK for Gabagool Apps monetization: interstitial, rewarded and banner ads with server-side config, price floors and gating, on top of Google Mobile Ads (Ad Manager / AdMob).",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "react-native": "src/index.ts",
9
+ "files": [
10
+ "dist",
11
+ "src",
12
+ "CHANGELOG.md"
13
+ ],
14
+ "license": "Apache-2.0",
15
+ "homepage": "https://gabagool.com.br",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "npm run build --prefix ../core && tsc -p tsconfig.build.json",
21
+ "typecheck": "tsc --noEmit -p tsconfig.json",
22
+ "test": "vitest run --root ../.. packages/react-native/test",
23
+ "prepack": "npm run build"
24
+ },
25
+ "dependencies": {
26
+ "@gabagool-sdk/core": "^0.1.0"
27
+ },
28
+ "peerDependencies": {
29
+ "@react-native-async-storage/async-storage": "^3.1.1",
30
+ "react": ">=18",
31
+ "react-native": ">=0.86",
32
+ "react-native-google-mobile-ads": "^17.4.0",
33
+ "react-native-play-install-referrer": "^2.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "@react-native-async-storage/async-storage": "^3.1.1",
37
+ "@types/react": "^19.0.0",
38
+ "react": "^19.0.0",
39
+ "react-native": "^0.86.0",
40
+ "react-native-google-mobile-ads": "^17.4.0",
41
+ "react-native-play-install-referrer": "^2.0.1"
42
+ }
43
+ }
package/src/Banner.tsx ADDED
@@ -0,0 +1,82 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import {
3
+ BannerAd as AdMobBannerAd,
4
+ BannerAdSize,
5
+ GAMBannerAd,
6
+ RevenuePrecisions,
7
+ } from 'react-native-google-mobile-ads';
8
+ import { networkOf, type AdHandle } from '@gabagool-sdk/core';
9
+ import { BannerFlow } from './bannerFlow.js';
10
+ import { GabagoolAds } from './facade.js';
11
+ type PaidEvent = {
12
+ currency: string;
13
+ precision: RevenuePrecisions;
14
+ value: number;
15
+ valueMicros?: string | null;
16
+ responseInfo?: { loadedAdapterResponse?: { adSourceName?: string | null } | null };
17
+ };
18
+
19
+ export interface BannerProps {
20
+ screen: string;
21
+ slot?: string;
22
+ size?: string;
23
+ onLoaded?: () => void;
24
+ onFailedToLoad?: (error: Error) => void;
25
+ }
26
+
27
+ const precisionName = (value: RevenuePrecisions): string => ({
28
+ [RevenuePrecisions.ESTIMATED]: 'estimated',
29
+ [RevenuePrecisions.PRECISE]: 'precise',
30
+ [RevenuePrecisions.PUBLISHER_PROVIDED]: 'publisher_provided',
31
+ [RevenuePrecisions.UNKNOWN]: 'unknown',
32
+ })[value] ?? 'unknown';
33
+
34
+ /** Banner declarativo: primary -> fallback; banners nunca recebem KV de piso. */
35
+ export function Banner({ screen, slot = 'banner', size = BannerAdSize.ANCHORED_ADAPTIVE_BANNER, onLoaded, onFailedToLoad }: BannerProps) {
36
+ const [handle, setHandle] = useState<AdHandle | null>(null);
37
+ const callbacks = useRef({ onLoaded, onFailedToLoad });
38
+ callbacks.current = { onLoaded, onFailedToLoad };
39
+ const flow = useRef<BannerFlow | null>(null);
40
+
41
+ useEffect(() => {
42
+ const next = new BannerFlow(
43
+ {
44
+ ready: () => GabagoolAds.whenReady(),
45
+ bannerFor: (s, sl) => GabagoolAds.bannerFor(s, sl),
46
+ markFailed: (ad) => GabagoolAds.nativeAdapter()?.markBannerFailed(ad),
47
+ trackLoad: (unit, status) => GabagoolAds.core.trackBannerLoad(unit, status),
48
+ },
49
+ screen,
50
+ slot,
51
+ {
52
+ setHandle,
53
+ onLoaded: () => callbacks.current.onLoaded?.(),
54
+ onFailedToLoad: (error) => callbacks.current.onFailedToLoad?.(error),
55
+ },
56
+ );
57
+ flow.current = next;
58
+ next.start();
59
+ return () => { next.stop(); flow.current = null; };
60
+ }, [screen, slot]);
61
+
62
+ const failed = (error: Error) => flow.current?.failed(error);
63
+ const loaded = () => flow.current?.loaded();
64
+ const paid = (event: PaidEvent) => {
65
+ if (!handle) return;
66
+ const parsed = event.valueMicros == null ? Number.NaN : Number(event.valueMicros);
67
+ const micros = Number.isFinite(parsed) ? parsed : Math.round(event.value * 1_000_000);
68
+ void GabagoolAds.core.trackBannerPaid(
69
+ handle.adUnit,
70
+ micros,
71
+ event.currency,
72
+ precisionName(event.precision),
73
+ event.responseInfo?.loadedAdapterResponse?.adSourceName ?? undefined,
74
+ ).catch(() => undefined);
75
+ };
76
+
77
+ if (!handle) return null;
78
+ const common = { key: handle.id, unitId: handle.adUnit, requestOptions: undefined, onAdLoaded: loaded, onAdFailedToLoad: failed, onPaid: paid };
79
+ return networkOf(handle.adUnit) === 'gam'
80
+ ? React.createElement(GAMBannerAd, { ...common, sizes: [size] })
81
+ : React.createElement(AdMobBannerAd, { ...common, size });
82
+ }