@amplytools/react-native-amply-sdk 0.14.0 → 0.15.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.
Files changed (78) hide show
  1. package/AmplyReactNative.podspec +1 -1
  2. package/README.md +41 -0
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +22 -0
  5. package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +11 -0
  6. package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +29 -0
  7. package/android/src/main/java/tools/amply/sdk/reactnative/model/CampaignTouchPayload.kt +12 -0
  8. package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +8 -0
  9. package/android/src/newarch/jni/AmplyReactNative-generated.cpp +12 -0
  10. package/dist/cjs/campaignTouch.js +44 -0
  11. package/dist/cjs/campaignTouch.js.map +1 -0
  12. package/dist/cjs/hooks/useAmplySystemEvents.js +60 -0
  13. package/dist/cjs/hooks/useAmplySystemEvents.js.map +1 -0
  14. package/dist/cjs/index.js +723 -0
  15. package/dist/cjs/index.js.map +1 -0
  16. package/dist/cjs/nativeModule.js +55 -0
  17. package/dist/cjs/nativeModule.js.map +1 -0
  18. package/dist/cjs/nativeSpecs/NativeAmplyModule.js +5 -0
  19. package/dist/cjs/nativeSpecs/NativeAmplyModule.js.map +1 -0
  20. package/dist/cjs/systemEventUtils.js +47 -0
  21. package/dist/cjs/systemEventUtils.js.map +1 -0
  22. package/dist/cjs/systemEvents.js +27 -0
  23. package/dist/cjs/systemEvents.js.map +1 -0
  24. package/dist/src/campaignTouch.d.ts +36 -0
  25. package/dist/src/campaignTouch.d.ts.map +1 -0
  26. package/dist/src/campaignTouch.js +39 -0
  27. package/dist/src/index.d.ts +35 -0
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +120 -0
  30. package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +28 -0
  31. package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -1
  32. package/dist/src/systemEventUtils.d.ts.map +1 -1
  33. package/dist/src/systemEventUtils.js +3 -1
  34. package/docs/ARCHITECTURE.md +28 -19
  35. package/docs/DEVELOPMENT.md +2 -1
  36. package/ios/Sources/AmplyReactNative/AmplyModule.mm +56 -0
  37. package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +14 -0
  38. package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +4 -0
  39. package/package.json +12 -6
  40. package/scripts/check-package.js +159 -0
  41. package/src/campaignTouch.ts +74 -0
  42. package/src/index.ts +124 -0
  43. package/src/nativeSpecs/NativeAmplyModule.ts +29 -0
  44. package/src/systemEventUtils.ts +3 -1
  45. package/dist/index.js +0 -272
  46. package/dist/index.js.map +0 -1
  47. package/dist/index.mjs +0 -234
  48. package/dist/index.mjs.map +0 -1
  49. package/dist/plugin/index.d.ts +0 -6
  50. package/dist/plugin/index.d.ts.map +0 -1
  51. package/dist/plugin/index.js +0 -186
  52. package/dist/plugin/index.js.map +0 -1
  53. package/dist/plugin/index.mjs +0 -169
  54. package/dist/plugin/index.mjs.map +0 -1
  55. package/dist/plugin/withAmply.d.ts +0 -12
  56. package/dist/plugin/withAmply.d.ts.map +0 -1
  57. package/dist/src/__tests__/appLanguage.test.d.ts +0 -43
  58. package/dist/src/__tests__/appLanguage.test.d.ts.map +0 -1
  59. package/dist/src/__tests__/appLanguage.test.js +0 -211
  60. package/dist/src/__tests__/campaignResolved.test.d.ts +0 -2
  61. package/dist/src/__tests__/campaignResolved.test.d.ts.map +0 -1
  62. package/dist/src/__tests__/campaignResolved.test.js +0 -208
  63. package/dist/src/__tests__/gateRegistrationLifecycle.test.d.ts +0 -2
  64. package/dist/src/__tests__/gateRegistrationLifecycle.test.d.ts.map +0 -1
  65. package/dist/src/__tests__/gateRegistrationLifecycle.test.js +0 -215
  66. package/dist/src/__tests__/index.test.d.ts +0 -2
  67. package/dist/src/__tests__/index.test.d.ts.map +0 -1
  68. package/dist/src/__tests__/index.test.js +0 -204
  69. package/dist/src/__tests__/systemEventUtils.test.d.ts +0 -2
  70. package/dist/src/__tests__/systemEventUtils.test.d.ts.map +0 -1
  71. package/dist/src/__tests__/systemEventUtils.test.js +0 -56
  72. package/dist/tsconfig.tsbuildinfo +0 -1
  73. package/plugin/tsconfig.tsbuildinfo +0 -1
  74. package/src/__tests__/appLanguage.test.ts +0 -296
  75. package/src/__tests__/campaignResolved.test.ts +0 -294
  76. package/src/__tests__/gateRegistrationLifecycle.test.ts +0 -281
  77. package/src/__tests__/index.test.ts +0 -258
  78. package/src/__tests__/systemEventUtils.test.ts +0 -75
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../plugin/withAmply.ts","../../plugin/index.ts"],"sourcesContent":["import {\n ConfigPlugin,\n withGradleProperties,\n withProjectBuildGradle,\n withMainApplication,\n withSettingsGradle,\n withAppBuildGradle,\n AndroidConfig,\n} from '@expo/config-plugins';\nimport { mergeContents } from '@expo/config-plugins/build/utils/generateCode';\nimport { addImports } from '@expo/config-plugins/build/android/codeMod';\n\nconst DEFAULT_SDK_VERSION = '0.1.7';\nconst GITHUB_MAVEN_URL = 'https://maven.pkg.github.com/amply/sdk';\nconst GITHUB_MAVEN_CREDENTIALS_ENV = {\n username: 'AMPLY_MAVEN_USERNAME',\n password: 'AMPLY_MAVEN_PASSWORD',\n};\n\nconst GITHUB_GRADLE_REPOSITORY_SNIPPET = `\n maven {\n url '${GITHUB_MAVEN_URL}'\n credentials {\n username findProperty('${GITHUB_MAVEN_CREDENTIALS_ENV.username}') ?: System.getenv('${GITHUB_MAVEN_CREDENTIALS_ENV.username}')\n password findProperty('${GITHUB_MAVEN_CREDENTIALS_ENV.password}') ?: System.getenv('${GITHUB_MAVEN_CREDENTIALS_ENV.password}')\n }\n }\n`;\n\nconst GRADLE_DEPENDENCY_LINE = ` implementation(\"tools.amply:sdk-android:${'$'}{project.findProperty('amplySdkVersion') ?: System.getenv('AMPLY_SDK_VERSION') ?: '${DEFAULT_SDK_VERSION}'}\")`;\n\nfunction applyGithubMavenRepository(contents: string): string {\n if (contents.includes(GITHUB_MAVEN_URL)) {\n return contents;\n }\n\n const repositoriesBlock = /(repositories\\s*\\{[\\s\\S]*?\\n\\})/;\n if (repositoriesBlock.test(contents)) {\n return contents.replace(repositoriesBlock, match => {\n if (match.includes(GITHUB_MAVEN_URL)) {\n return match;\n }\n return match.replace(/\\n\\}/, `\\n${GITHUB_GRADLE_REPOSITORY_SNIPPET} }`);\n });\n }\n\n // Fallback: append at the end of settings block\n return `${contents}\\nrepositories {${GITHUB_GRADLE_REPOSITORY_SNIPPET}}\\n`;\n}\n\nfunction applyDependency(contents: string): string {\n if (contents.includes('tools.amply:sdk-android')) {\n return contents;\n }\n\n const dependenciesBlock = /(dependencies\\s*\\{[\\s\\S]*?\\n\\})/;\n if (dependenciesBlock.test(contents)) {\n return contents.replace(dependenciesBlock, match => {\n if (match.includes('tools.amply:sdk-android')) {\n return match;\n }\n return match.replace(/\\n\\}/, `\\n${GRADLE_DEPENDENCY_LINE}\\n}`);\n });\n }\n\n return `${contents}\\ndependencies {\\n${GRADLE_DEPENDENCY_LINE}\\n}\\n`;\n}\n\ntype WithAmplyPluginProps = {\n sdkVersion?: string;\n /**\n * Optional escape hatch for internal builds that still rely on GitHub Packages.\n * Public installs resolve from Maven Central without any additional repositories.\n */\n useGithubFallbackRepo?: boolean;\n};\n\nfunction addAmplyPackageRegistration(contents: string, language: 'java' | 'kt'): string {\n // Check if AmplyPackage is already registered\n if (contents.includes('AmplyPackage')) {\n console.log('[Amply] AmplyPackage already registered in MainApplication');\n return contents;\n }\n\n // Add import if not present\n if (!contents.includes('import com.amply.reactnative.AmplyPackage')) {\n contents = addImports(contents, ['com.amply.reactnative.AmplyPackage'], language === 'java');\n console.log('[Amply] Added AmplyPackage import to MainApplication');\n }\n\n // Determine the correct syntax for the language\n const amplyPackageCall = language === 'java' ? 'add(new AmplyPackage());' : 'add(AmplyPackage())';\n\n // Add AmplyPackage() to the packages list in getPackages() method\n // Use mergeContents for safe, idempotent insertion\n const result = mergeContents({\n src: contents,\n newSrc: amplyPackageCall,\n tag: 'amply-package-registration',\n anchor: /(?<=\\.apply\\s*\\{[\\s]*)/,\n offset: 0,\n comment: language === 'java' ? '//' : '//',\n });\n\n if (result.didMerge) {\n console.log('[Amply] Registered AmplyPackage in getPackages() method');\n return result.contents;\n } else {\n console.warn('[Amply] Could not register AmplyPackage - anchor pattern not found');\n return contents;\n }\n}\n\nconst withAmply: ConfigPlugin<WithAmplyPluginProps> = (config, props = {}) => {\n const {sdkVersion, useGithubFallbackRepo = false} = props;\n const version = sdkVersion ?? DEFAULT_SDK_VERSION;\n\n config = withGradleProperties(config, gradleConfig => {\n const exists = gradleConfig.modResults.find(\n item => item.type === 'property' && item.key === 'amplySdkVersion'\n );\n if (!exists) {\n gradleConfig.modResults.push({ type: 'property', key: 'amplySdkVersion', value: version });\n }\n return gradleConfig;\n });\n\n config = withProjectBuildGradle(config, gradleConfig => {\n if (useGithubFallbackRepo) {\n gradleConfig.modResults.contents = applyGithubMavenRepository(gradleConfig.modResults.contents);\n }\n gradleConfig.modResults.contents = applyDependency(gradleConfig.modResults.contents);\n return gradleConfig;\n });\n\n // Add Amply native module to settings.gradle\n config = withSettingsGradle(config, gradleConfig => {\n const contents = gradleConfig.modResults.contents;\n\n // Only add if not already present\n if (contents.includes(':amplyreactnative')) {\n console.log('[Amply] Amply module already configured in settings.gradle');\n return gradleConfig;\n }\n\n // Add the Amply native module include configuration after the last includeBuild statement\n const moduleConfig = \"\\n\\ninclude ':amplyreactnative'\\nproject(':amplyreactnative').projectDir = new File(rootProject.projectDir, '../../../android')\";\n\n const lastIncludeBuildMatch = contents.lastIndexOf('includeBuild');\n let modified = contents;\n\n if (lastIncludeBuildMatch !== -1) {\n const nextNewlineAfterIncludeBuild = contents.indexOf('\\n', lastIncludeBuildMatch);\n if (nextNewlineAfterIncludeBuild !== -1) {\n modified =\n contents.slice(0, nextNewlineAfterIncludeBuild + 1) +\n moduleConfig +\n contents.slice(nextNewlineAfterIncludeBuild + 1);\n console.log('[Amply] Added Amply module include to settings.gradle');\n }\n } else {\n // Fallback: append at the end\n modified = contents + moduleConfig;\n console.log('[Amply] Appended Amply module include to settings.gradle (no includeBuild found)');\n }\n\n gradleConfig.modResults.contents = modified;\n return gradleConfig;\n });\n\n // Add Amply native module dependency to app/build.gradle\n config = withAppBuildGradle(config, gradleConfig => {\n const contents = gradleConfig.modResults.contents;\n\n // Only add if not already present\n if (contents.includes(':amplyreactnative')) {\n console.log('[Amply] Amply module already configured in app/build.gradle');\n return gradleConfig;\n }\n\n // Add the Amply native module dependency after the first implementation statement\n const depsStart = contents.indexOf('dependencies {');\n if (depsStart !== -1) {\n const firstImplAfterDeps = contents.indexOf('implementation', depsStart);\n if (firstImplAfterDeps !== -1) {\n const lineEnd = contents.indexOf('\\n', firstImplAfterDeps);\n if (lineEnd !== -1) {\n const moduleImpl = \"\\n\\n // Amply React Native module\\n implementation project(':amplyreactnative')\";\n gradleConfig.modResults.contents =\n contents.slice(0, lineEnd) +\n moduleImpl +\n contents.slice(lineEnd);\n console.log('[Amply] Added Amply module dependency to app/build.gradle');\n }\n }\n }\n\n return gradleConfig;\n });\n\n // Register AmplyPackage in MainApplication using proper Expo API\n // This hook runs AFTER the MainApplication file is generated by Expo\n config = withMainApplication(config, async config => {\n const { contents, language } = config.modResults;\n const modified = addAmplyPackageRegistration(contents, language);\n config.modResults.contents = modified;\n return config;\n });\n\n return config;\n};\n\nexport default withAmply;\n","import type {ConfigPlugin} from '@expo/config-plugins';\nimport withAmply from './withAmply';\n\nconst plugin: ConfigPlugin<{ sdkVersion?: string }> = (config, props) =>\n withAmply(config, props);\n\nexport default plugin;\n"],"mappings":"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","names":["config"]}
@@ -1,12 +0,0 @@
1
- import { ConfigPlugin } from '@expo/config-plugins';
2
- type WithAmplyPluginProps = {
3
- sdkVersion?: string;
4
- /**
5
- * Optional escape hatch for internal builds that still rely on GitHub Packages.
6
- * Public installs resolve from Maven Central without any additional repositories.
7
- */
8
- useGithubFallbackRepo?: boolean;
9
- };
10
- declare const withAmply: ConfigPlugin<WithAmplyPluginProps>;
11
- export default withAmply;
12
- //# sourceMappingURL=withAmply.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"withAmply.d.ts","sourceRoot":"","sources":["../../plugin/withAmply.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAOb,MAAM,sBAAsB,CAAC;AA4D9B,KAAK,oBAAoB,GAAG;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAsCF,QAAA,MAAM,SAAS,EAAE,YAAY,CAAC,oBAAoB,CAiGjD,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -1,43 +0,0 @@
1
- /**
2
- * `setAppLanguage` and the `appLanguage` initialization option.
3
- *
4
- * The SDK treats both as one setting with "last write wins": `appLanguage` in the config is the
5
- * same as calling `setAppLanguage` at initialization. The native bridges cannot honour that order
6
- * on their own for a call made BEFORE `initialize` — they buffer it and replay it after the SDK is
7
- * built, which would let an earlier call overwrite the later config value. So the JS layer parks a
8
- * pre-init call itself and folds it into the config it hands to native.
9
- *
10
- * Each test loads a fresh copy of the module: the parked value is module state, and leaking it
11
- * from one test into the next would make every ordering assertion here meaningless.
12
- */
13
- type AmplyModule = typeof import('../index');
14
- declare function createNativeModule(): {
15
- initialize: jest.Mock<Promise<void>, [], any>;
16
- isInitialized: jest.Mock<boolean, [], any>;
17
- setAppLanguage: jest.Mock<any, any, any>;
18
- setUserId: jest.Mock<any, any, any>;
19
- addListener: jest.Mock<any, any, any>;
20
- removeListeners: jest.Mock<any, any, any>;
21
- onSystemEvent: jest.Mock<{
22
- remove: jest.Mock<any, any, any>;
23
- }, [], any>;
24
- onDeepLink: jest.Mock<{
25
- remove: jest.Mock<any, any, any>;
26
- }, [], any>;
27
- onCampaignPresent: jest.Mock<{
28
- remove: jest.Mock<any, any, any>;
29
- }, [], any>;
30
- /** Test-only: pretend the SDK is (or is not) up without going through initialize. */
31
- setInitialized(value: boolean): void;
32
- };
33
- type NativeModuleMock = ReturnType<typeof createNativeModule>;
34
- declare const KEYS: {
35
- appId: string;
36
- apiKeyPublic: string;
37
- apiKeySecret: string;
38
- };
39
- declare function load(): {
40
- Amply: AmplyModule;
41
- native: NativeModuleMock;
42
- };
43
- //# sourceMappingURL=appLanguage.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appLanguage.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/appLanguage.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,KAAK,WAAW,GAAG,cAAc,UAAU,CAAC,CAAC;AAE7C,iBAAS,kBAAkB;;;;;;;;;;;;;;;;IAcvB,qFAAqF;0BAC/D,OAAO;EAIhC;AAED,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,QAAA,MAAM,IAAI;;;;CAAgE,CAAC;AAE3E,iBAAS,IAAI,IAAI;IAAC,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAC,CAU9D"}
@@ -1,211 +0,0 @@
1
- "use strict";
2
- function createNativeModule() {
3
- let initialized = false;
4
- return {
5
- initialize: jest.fn(async () => {
6
- initialized = true;
7
- }),
8
- isInitialized: jest.fn(() => initialized),
9
- setAppLanguage: jest.fn(),
10
- setUserId: jest.fn(),
11
- addListener: jest.fn(),
12
- removeListeners: jest.fn(),
13
- onSystemEvent: jest.fn(() => ({ remove: jest.fn() })),
14
- onDeepLink: jest.fn(() => ({ remove: jest.fn() })),
15
- onCampaignPresent: jest.fn(() => ({ remove: jest.fn() })),
16
- /** Test-only: pretend the SDK is (or is not) up without going through initialize. */
17
- setInitialized(value) {
18
- initialized = value;
19
- },
20
- };
21
- }
22
- const KEYS = { appId: 'id', apiKeyPublic: 'public', apiKeySecret: 'secret' };
23
- function load() {
24
- let Amply;
25
- const native = createNativeModule();
26
- jest.isolateModules(() => {
27
- // eslint-disable-next-line @typescript-eslint/no-var-requires
28
- require('../nativeModule').__setNativeModule(native);
29
- // eslint-disable-next-line @typescript-eslint/no-var-requires
30
- Amply = require('../index');
31
- });
32
- return { Amply, native };
33
- }
34
- describe('setAppLanguage after initialization', () => {
35
- it.each(['pt_BR', 'zh-TW', 'FR', 'Français'])('forwards %p to the native SDK verbatim — parsing and the warning for junk are the SDK’s job', tag => {
36
- const { Amply, native } = load();
37
- native.setInitialized(true);
38
- Amply.setAppLanguage(tag);
39
- expect(native.setAppLanguage).toHaveBeenCalledTimes(1);
40
- expect(native.setAppLanguage).toHaveBeenCalledWith(tag);
41
- });
42
- it('forwards null, which returns the SDK to automatic detection', () => {
43
- const { Amply, native } = load();
44
- native.setInitialized(true);
45
- Amply.setAppLanguage(null);
46
- expect(native.setAppLanguage).toHaveBeenCalledWith(null);
47
- });
48
- it('is on the default export as well', () => {
49
- const { Amply, native } = load();
50
- native.setInitialized(true);
51
- Amply.default.setAppLanguage('de');
52
- expect(native.setAppLanguage).toHaveBeenCalledWith('de');
53
- });
54
- });
55
- describe('appLanguage in the initialization config', () => {
56
- it('is passed through to the native initialize', async () => {
57
- const { Amply, native } = load();
58
- await Amply.initialize({ ...KEYS, appLanguage: 'pt_BR' });
59
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'pt_BR' }));
60
- expect(native.setAppLanguage).not.toHaveBeenCalled();
61
- });
62
- it('is left out of the native config when the caller did not give one', async () => {
63
- const { Amply, native } = load();
64
- await Amply.initialize(KEYS);
65
- const [config] = native.initialize.mock.calls[0];
66
- expect(config).not.toHaveProperty('appLanguage');
67
- });
68
- });
69
- describe('setAppLanguage before initialization', () => {
70
- it('is not sent to a native SDK that is not up yet', () => {
71
- const { Amply, native } = load();
72
- Amply.setAppLanguage('fr');
73
- expect(native.setAppLanguage).not.toHaveBeenCalled();
74
- });
75
- it('reaches the SDK as the config appLanguage when initialize names none', async () => {
76
- const { Amply, native } = load();
77
- Amply.setAppLanguage('fr');
78
- await Amply.initialize(KEYS);
79
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'fr' }));
80
- expect(native.setAppLanguage).not.toHaveBeenCalled();
81
- });
82
- it('keeps only the last of several calls', async () => {
83
- const { Amply, native } = load();
84
- Amply.setAppLanguage('fr');
85
- Amply.setAppLanguage('de');
86
- await Amply.initialize(KEYS);
87
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'de' }));
88
- });
89
- it('loses to an appLanguage given to the later initialize call', async () => {
90
- const { Amply, native } = load();
91
- Amply.setAppLanguage('fr');
92
- await Amply.initialize({ ...KEYS, appLanguage: 'de' });
93
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'de' }));
94
- expect(native.setAppLanguage).not.toHaveBeenCalled();
95
- });
96
- it('wins over a blank appLanguage in the config, which names no language', async () => {
97
- const { Amply, native } = load();
98
- Amply.setAppLanguage('fr');
99
- await Amply.initialize({ ...KEYS, appLanguage: ' ' });
100
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'fr' }));
101
- });
102
- // null cannot travel in the config — there it means "leave the setting alone" — so a parked
103
- // clear is applied as a call once the SDK is up.
104
- it('applies a parked null right after initialization', async () => {
105
- const { Amply, native } = load();
106
- Amply.setAppLanguage(null);
107
- await Amply.initialize(KEYS);
108
- const [config] = native.initialize.mock.calls[0];
109
- expect(config).not.toHaveProperty('appLanguage');
110
- expect(native.setAppLanguage).toHaveBeenCalledTimes(1);
111
- expect(native.setAppLanguage).toHaveBeenCalledWith(null);
112
- });
113
- it('drops a parked null when the later initialize names a language', async () => {
114
- const { Amply, native } = load();
115
- Amply.setAppLanguage(null);
116
- await Amply.initialize({ ...KEYS, appLanguage: 'de' });
117
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'de' }));
118
- expect(native.setAppLanguage).not.toHaveBeenCalled();
119
- });
120
- it('survives an initialize that fails, and is used by the retry', async () => {
121
- const { Amply, native } = load();
122
- Amply.setAppLanguage('fr');
123
- native.initialize.mockImplementationOnce(async () => {
124
- throw new Error('AMP_INIT_FAILED');
125
- });
126
- await expect(Amply.initialize(KEYS)).rejects.toThrow('AMP_INIT_FAILED');
127
- await Amply.initialize(KEYS);
128
- expect(native.initialize).toHaveBeenLastCalledWith(expect.objectContaining({ appLanguage: 'fr' }));
129
- });
130
- it('survives an initialize rejected for a missing key', async () => {
131
- const { Amply, native } = load();
132
- const error = jest.spyOn(console, 'error').mockImplementation(() => { });
133
- Amply.setAppLanguage('fr');
134
- await expect(Amply.initialize({ ...KEYS, apiKeySecret: '' })).rejects.toMatchObject({
135
- code: 'AMP_INVALID_CONFIG',
136
- });
137
- await Amply.initialize(KEYS);
138
- expect(native.initialize).toHaveBeenCalledTimes(1);
139
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'fr' }));
140
- error.mockRestore();
141
- });
142
- });
143
- describe('setAppLanguage while initialize is in flight', () => {
144
- it('is applied after initialization, so it wins over the config value it came after', async () => {
145
- const { Amply, native } = load();
146
- let finishInit;
147
- native.initialize.mockImplementationOnce(() => new Promise(resolve => {
148
- finishInit = () => {
149
- native.setInitialized(true);
150
- resolve();
151
- };
152
- }));
153
- const init = Amply.initialize({ ...KEYS, appLanguage: 'de' });
154
- Amply.setAppLanguage('fr');
155
- expect(native.setAppLanguage).not.toHaveBeenCalled();
156
- finishInit();
157
- await init;
158
- expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({ appLanguage: 'de' }));
159
- expect(native.setAppLanguage).toHaveBeenCalledTimes(1);
160
- expect(native.setAppLanguage).toHaveBeenCalledWith('fr');
161
- });
162
- // The native SDK reports itself up before the initialize promise settles, so a call made in
163
- // that window goes straight through. A value held from before initialize is older than it and
164
- // must not be replayed over it when the promise finally resolves.
165
- function hangInitialize(native) {
166
- const control = { comeUp: () => { }, resolve: () => { }, reject: (_error) => { } };
167
- native.initialize.mockImplementationOnce(() => new Promise((resolve, reject) => {
168
- control.comeUp = () => native.setInitialized(true);
169
- control.resolve = resolve;
170
- control.reject = reject;
171
- }));
172
- return control;
173
- }
174
- it('does not replay an older held null over a call the SDK already received', async () => {
175
- const { Amply, native } = load();
176
- Amply.setAppLanguage(null);
177
- const init = hangInitialize(native);
178
- const pending = Amply.initialize(KEYS);
179
- init.comeUp();
180
- Amply.setAppLanguage('fr');
181
- init.resolve();
182
- await pending;
183
- expect(native.setAppLanguage.mock.calls).toEqual([['fr']]);
184
- });
185
- it('does not replay an older held tag over a call the SDK already received', async () => {
186
- const { Amply, native } = load();
187
- const init = hangInitialize(native);
188
- const pending = Amply.initialize(KEYS);
189
- Amply.setAppLanguage('de');
190
- init.comeUp();
191
- Amply.setAppLanguage('fr');
192
- init.resolve();
193
- await pending;
194
- expect(native.setAppLanguage.mock.calls).toEqual([['fr']]);
195
- });
196
- it('does not hand a superseded held tag to the retry of a failed initialize', async () => {
197
- const { Amply, native } = load();
198
- Amply.setAppLanguage('de');
199
- const init = hangInitialize(native);
200
- const pending = Amply.initialize(KEYS);
201
- init.comeUp();
202
- Amply.setAppLanguage('fr');
203
- init.reject(new Error('AMP_INIT_FAILED'));
204
- await expect(pending).rejects.toThrow('AMP_INIT_FAILED');
205
- native.setInitialized(false);
206
- await Amply.initialize(KEYS);
207
- const [retryConfig] = native.initialize.mock.calls[1];
208
- expect(retryConfig).not.toHaveProperty('appLanguage');
209
- expect(native.setAppLanguage.mock.calls).toEqual([['fr']]);
210
- });
211
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=campaignResolved.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"campaignResolved.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/campaignResolved.test.ts"],"names":[],"mappings":""}
@@ -1,208 +0,0 @@
1
- import * as Amply from '../index';
2
- import { __setNativeModule } from '../nativeModule';
3
- // Drains the microtask queue a few times so promise-chained presenter resolutions
4
- // (sync or async) settle before assertions run.
5
- async function flushMicrotasks() {
6
- for (let i = 0; i < 5; i++) {
7
- await Promise.resolve();
8
- }
9
- }
10
- const mockNativeModule = {
11
- initialize: jest.fn(),
12
- isInitialized: jest.fn(() => true),
13
- track: jest.fn(),
14
- trackGated: jest.fn(),
15
- resolveCampaign: jest.fn(),
16
- getRecentEvents: jest.fn(),
17
- getDataSetSnapshot: jest.fn(),
18
- registerDeepLinkListener: jest.fn(),
19
- registerGate: jest.fn(),
20
- unregisterGate: jest.fn(),
21
- addListener: jest.fn(),
22
- removeListeners: jest.fn(),
23
- onSystemEvent: jest.fn(),
24
- onDeepLink: jest.fn(),
25
- onCampaignPresent: jest.fn(),
26
- setCustomProperties: jest.fn(),
27
- getCustomProperty: jest.fn(),
28
- removeCustomProperty: jest.fn(),
29
- clearCustomProperties: jest.fn(),
30
- };
31
- describe('Amply gate API (trackGated + registerGate)', () => {
32
- afterEach(() => {
33
- jest.clearAllMocks();
34
- Amply.removeAllListeners();
35
- __setNativeModule(null);
36
- });
37
- beforeEach(() => {
38
- __setNativeModule(mockNativeModule);
39
- });
40
- describe('trackEvent (fire-and-forget)', () => {
41
- it('forwards to native track and never gates', async () => {
42
- await Amply.trackEvent('SaveTapped', { foo: 'bar' });
43
- expect(mockNativeModule.track).toHaveBeenCalledWith({
44
- name: 'SaveTapped',
45
- properties: { foo: 'bar' },
46
- });
47
- expect(mockNativeModule.trackGated).not.toHaveBeenCalled();
48
- });
49
- it('defaults missing properties to an empty object', async () => {
50
- await Amply.trackEvent('Ping');
51
- expect(mockNativeModule.track).toHaveBeenCalledWith({
52
- name: 'Ping',
53
- properties: {},
54
- });
55
- });
56
- });
57
- describe('trackGated', () => {
58
- it('resolves proceed/completed when the gate is satisfied', async () => {
59
- mockNativeModule.trackGated.mockResolvedValueOnce({
60
- outcome: 'proceed',
61
- reason: 'completed',
62
- });
63
- const decision = await Amply.trackGated('SaveTapped', { foo: 'bar' });
64
- expect(mockNativeModule.trackGated).toHaveBeenCalledWith('SaveTapped', {
65
- foo: 'bar',
66
- });
67
- expect(decision).toEqual({ outcome: 'proceed', reason: 'completed' });
68
- });
69
- it('resolves proceed/failOpen when the native layer reports fail-open', async () => {
70
- mockNativeModule.trackGated.mockResolvedValueOnce({
71
- outcome: 'proceed',
72
- reason: 'failOpen',
73
- });
74
- const decision = await Amply.trackGated('SaveTapped', {});
75
- expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
76
- });
77
- it('resolves cancelled on a user dismiss under onAbort=cancel', async () => {
78
- mockNativeModule.trackGated.mockResolvedValueOnce({ outcome: 'cancelled' });
79
- const decision = await Amply.trackGated('SaveTapped', undefined);
80
- expect(mockNativeModule.trackGated).toHaveBeenCalledWith('SaveTapped', {});
81
- expect(decision).toEqual({ outcome: 'cancelled' });
82
- });
83
- it('never rejects — fails open to proceed/failOpen when native rejects', async () => {
84
- mockNativeModule.trackGated.mockRejectedValueOnce(new Error('boom'));
85
- const decision = await Amply.trackGated('SaveTapped', {});
86
- expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
87
- });
88
- it('fails open on an unexpected/malformed native decision', async () => {
89
- mockNativeModule.trackGated.mockResolvedValueOnce({ outcome: 'wat' });
90
- const decision = await Amply.trackGated('SaveTapped', {});
91
- expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
92
- });
93
- it('normalizes an unknown proceed reason to failOpen', async () => {
94
- mockNativeModule.trackGated.mockResolvedValueOnce({
95
- outcome: 'proceed',
96
- reason: 'something-else',
97
- });
98
- const decision = await Amply.trackGated('SaveTapped', {});
99
- expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
100
- });
101
- });
102
- describe('registerGate', () => {
103
- it('registers on the native module and subscribes to onCampaignPresent', async () => {
104
- const remove = jest.fn();
105
- mockNativeModule.onCampaignPresent.mockReturnValue({ remove });
106
- const presenter = jest.fn();
107
- const unsubscribe = await Amply.registerGate('stillframe://ad', presenter);
108
- expect(mockNativeModule.registerGate).toHaveBeenCalledTimes(1);
109
- expect(mockNativeModule.registerGate).toHaveBeenCalledWith('stillframe://ad', 'cancel', 0);
110
- expect(mockNativeModule.onCampaignPresent).toHaveBeenCalledTimes(1);
111
- unsubscribe();
112
- expect(remove).toHaveBeenCalledTimes(1);
113
- expect(mockNativeModule.unregisterGate).toHaveBeenCalledWith('stillframe://ad');
114
- });
115
- // This used to assert the opposite — that a second registration re-subscribed but did
116
- // NOT re-register natively. That encoded the defect: the second subscriber joined the
117
- // shared onCampaignPresent channel while the first stayed on it, so one presentation was
118
- // handed to both presenters, and the second call's onAbort/timeout were silently dropped.
119
- // See https://trello.com/c/Gq589zwz; the full behaviour is covered in
120
- // gateRegistrationLifecycle.test.ts.
121
- it('replaces the previous registration when the same baseUrl is registered again', async () => {
122
- mockNativeModule.onCampaignPresent.mockReturnValue({ remove: jest.fn() });
123
- await Amply.registerGate('stillframe://ad', jest.fn());
124
- await Amply.registerGate('stillframe://ad', jest.fn(), { timeoutMs: 5000 });
125
- expect(mockNativeModule.unregisterGate).toHaveBeenCalledWith('stillframe://ad');
126
- expect(mockNativeModule.registerGate).toHaveBeenLastCalledWith('stillframe://ad', 'cancel', 5000);
127
- });
128
- it('passes through onAbort and timeoutMs options', async () => {
129
- mockNativeModule.onCampaignPresent.mockReturnValue({ remove: jest.fn() });
130
- await Amply.registerGate('stillframe://ad', jest.fn(), {
131
- onAbort: 'proceed',
132
- timeoutMs: 8000,
133
- });
134
- expect(mockNativeModule.registerGate).toHaveBeenCalledWith('stillframe://ad', 'proceed', 8000);
135
- });
136
- it('delivers params + info to the presenter and forwards completed', async () => {
137
- let emit;
138
- mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
139
- emit = listener;
140
- return { remove: jest.fn() };
141
- });
142
- const presenter = jest.fn((_params, _info, resolution) => resolution.completed());
143
- await Amply.registerGate('stillframe://ad', presenter);
144
- expect(emit).toBeDefined();
145
- emit({
146
- url: 'stillframe://ad?adUnit=rewarded_main',
147
- params: { adUnit: 'rewarded_main' },
148
- info: { campaignId: 'c1' },
149
- mediationId: 'm1',
150
- });
151
- await flushMicrotasks();
152
- expect(presenter).toHaveBeenCalledWith({ adUnit: 'rewarded_main' }, { campaignId: 'c1' }, expect.any(Object));
153
- expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m1', 'Completed');
154
- });
155
- it('forwards a dismissed resolution', async () => {
156
- let emit;
157
- mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
158
- emit = listener;
159
- return { remove: jest.fn() };
160
- });
161
- await Amply.registerGate('stillframe://ad', (_p, _i, resolution) => resolution.dismissed());
162
- emit({ url: 'stillframe://ad', params: {}, info: {}, mediationId: 'm2' });
163
- await flushMicrotasks();
164
- expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m2', 'Dismissed');
165
- });
166
- it('reports Unavailable (fail-open) when the presenter throws', async () => {
167
- let emit;
168
- mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
169
- emit = listener;
170
- return { remove: jest.fn() };
171
- });
172
- await Amply.registerGate('stillframe://ad', () => {
173
- throw new Error('presenter blew up');
174
- });
175
- emit({ url: 'stillframe://ad', params: {}, info: {}, mediationId: 'm3' });
176
- await flushMicrotasks();
177
- expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m3', 'Unavailable');
178
- });
179
- it('resolves at most once even if the presenter reports twice', async () => {
180
- let emit;
181
- mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
182
- emit = listener;
183
- return { remove: jest.fn() };
184
- });
185
- await Amply.registerGate('stillframe://ad', (_p, _i, resolution) => {
186
- resolution.completed();
187
- resolution.dismissed();
188
- });
189
- emit({ url: 'stillframe://ad', params: {}, info: {}, mediationId: 'm4' });
190
- await flushMicrotasks();
191
- expect(mockNativeModule.resolveCampaign).toHaveBeenCalledTimes(1);
192
- expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m4', 'Completed');
193
- });
194
- it('ignores dispatches whose url does not match the registered baseUrl', async () => {
195
- let emit;
196
- mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
197
- emit = listener;
198
- return { remove: jest.fn() };
199
- });
200
- const presenter = jest.fn();
201
- await Amply.registerGate('stillframe://ad', presenter);
202
- emit({ url: 'other://thing', params: {}, info: {}, mediationId: 'm5' });
203
- await flushMicrotasks();
204
- expect(presenter).not.toHaveBeenCalled();
205
- expect(mockNativeModule.resolveCampaign).not.toHaveBeenCalled();
206
- });
207
- });
208
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=gateRegistrationLifecycle.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gateRegistrationLifecycle.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/gateRegistrationLifecycle.test.ts"],"names":[],"mappings":""}