@amplytools/react-native-amply-sdk 0.11.0 → 0.12.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/AmplyReactNative.podspec +1 -1
- package/README.md +10 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +12 -3
- package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +23 -5
- package/dist/src/__tests__/gateRegistrationLifecycle.test.js +4 -4
- package/dist/src/__tests__/index.test.js +25 -1
- package/dist/src/index.d.ts +16 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +25 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/FEATURE_DEBUG.md +3 -1
- package/ios/Sources/AmplyReactNative/AmplyModule.mm +34 -7
- package/package.json +4 -3
- package/scripts/check-sample-parity.sh +263 -0
- package/src/__tests__/gateRegistrationLifecycle.test.ts +4 -4
- package/src/__tests__/index.test.ts +28 -1
- package/src/index.ts +40 -2
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Every control the sample spec requires must exist in every sample that claims the
|
|
4
|
+
# platform, tagged with its manifest id. Nothing else notices when one of them is missing.
|
|
5
|
+
#
|
|
6
|
+
# The six Amply samples are six copies of the same screen, and they drifted because
|
|
7
|
+
# copy-paste has no compiler. In the KMP repo, `ios-spm-sample/HomeViewController.swift`
|
|
8
|
+
# reached 886 lines while its CocoaPods twin sat at 566 — the same screen, forked and then
|
|
9
|
+
# edited on one side only, with `GateDemo.swift` existing in exactly one of them. Both
|
|
10
|
+
# projects built clean the whole time. Nobody noticed, because nothing checked. The two RN
|
|
11
|
+
# examples here forked the same way: `bare/src/hooks/useAmplyDemo.ts` and
|
|
12
|
+
# `expo/src/hooks/useAmplyDemo.ts` are two copies of one hook, and `bare` still imports the
|
|
13
|
+
# SDK under a legacy alias no integrator would type.
|
|
14
|
+
#
|
|
15
|
+
# This is that check. It greps each example's own sources for every manifest id required on
|
|
16
|
+
# the `rn` column and fails naming the id, the example, and where it looked. It is a parity
|
|
17
|
+
# check, not a UI test: it answers "is this control present in this sample", which is the
|
|
18
|
+
# only question consistency actually turns on.
|
|
19
|
+
#
|
|
20
|
+
# Spec: multiplatform-library-template/samples/SAMPLE_APP_SPEC.md (canonical, KMP repo)
|
|
21
|
+
# Manifest: example/sample-manifest.txt (verbatim copy of the KMP repo's canonical file;
|
|
22
|
+
# this script diffs the two when both are present)
|
|
23
|
+
#
|
|
24
|
+
# Usage: bash scripts/check-sample-parity.sh
|
|
25
|
+
set -uo pipefail
|
|
26
|
+
|
|
27
|
+
cd "$(dirname "$0")/.."
|
|
28
|
+
|
|
29
|
+
MANIFEST="example/sample-manifest.txt"
|
|
30
|
+
SPEC_PATH="multiplatform-library-template/samples/SAMPLE_APP_SPEC.md"
|
|
31
|
+
|
|
32
|
+
if [ ! -f "$MANIFEST" ]; then
|
|
33
|
+
echo "check-sample-parity: FAIL — missing $MANIFEST" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Quote characters an id can be wrapped in: JSX uses ", JS also uses ' and ` for template
|
|
38
|
+
# literals. The id literal must sit next to `testID` on one line.
|
|
39
|
+
Q="[\"'\`]"
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------------------
|
|
42
|
+
# The canonical copy. The manifest lives in the KMP repo; this repo carries a duplicate
|
|
43
|
+
# because it is published to npm and mirrored publicly and so cannot depend on a sibling
|
|
44
|
+
# checkout existing. Duplication that nothing checks is drift with extra steps, so: when
|
|
45
|
+
# the KMP repo is reachable, diff it and fail on any difference. When it is not, say so
|
|
46
|
+
# loudly and print the revision, so a stale copy shows up in the output instead of passing
|
|
47
|
+
# silently.
|
|
48
|
+
# ---------------------------------------------------------------------------------------
|
|
49
|
+
revision=$(grep -m1 '^# revision:' "$MANIFEST" | sed 's/^# revision:[[:space:]]*//')
|
|
50
|
+
if [ -z "$revision" ]; then
|
|
51
|
+
echo "check-sample-parity: FAIL — $MANIFEST has no '# revision:' header" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
peer=""
|
|
56
|
+
for candidate in \
|
|
57
|
+
"${AMPLY_SAMPLE_MANIFEST_PEER:-}" \
|
|
58
|
+
"../multiplatform-library-template/samples/sample-manifest.txt"
|
|
59
|
+
do
|
|
60
|
+
[ -n "$candidate" ] && [ -f "$candidate" ] && { peer="$candidate"; break; }
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
if [ -n "$peer" ]; then
|
|
64
|
+
if ! diff -u "$peer" "$MANIFEST" > /tmp/check-sample-parity-peer.diff 2>&1; then
|
|
65
|
+
echo "check-sample-parity: FAIL — this repo's manifest copy has diverged from the canonical one." >&2
|
|
66
|
+
echo " canonical: $peer" >&2
|
|
67
|
+
echo " copy: $MANIFEST" >&2
|
|
68
|
+
sed 's/^/ /' /tmp/check-sample-parity-peer.diff >&2
|
|
69
|
+
rm -f /tmp/check-sample-parity-peer.diff
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
rm -f /tmp/check-sample-parity-peer.diff
|
|
73
|
+
peer_state="verified byte-identical against $peer"
|
|
74
|
+
else
|
|
75
|
+
peer_state="NOT VERIFIED — the KMP repo was not found; set AMPLY_SAMPLE_MANIFEST_PEER to check it"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------------------
|
|
79
|
+
# Parse the manifest into: id <TAB> android <TAB> ios <TAB> rn <TAB> note
|
|
80
|
+
# ---------------------------------------------------------------------------------------
|
|
81
|
+
rows=$(
|
|
82
|
+
awk -F'|' '
|
|
83
|
+
/^[[:space:]]*#/ { next }
|
|
84
|
+
/^[[:space:]]*$/ { next }
|
|
85
|
+
NF < 4 { next }
|
|
86
|
+
{
|
|
87
|
+
for (i = 1; i <= NF; i++) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $i) }
|
|
88
|
+
note = (NF >= 5) ? $5 : ""
|
|
89
|
+
print $1 "\t" $2 "\t" $3 "\t" $4 "\t" note
|
|
90
|
+
}
|
|
91
|
+
' "$MANIFEST"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if [ -z "$rows" ]; then
|
|
95
|
+
echo "check-sample-parity: FAIL — parsed zero rows from $MANIFEST; the parser is broken" >&2
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
row_count=$(printf '%s\n' "$rows" | wc -l | tr -d ' ')
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------------------
|
|
102
|
+
# The examples. Each one searches its own sources plus the shared UI module. An absent root
|
|
103
|
+
# is skipped rather than fatal; an example with NO existing root at all is fatal, because
|
|
104
|
+
# that means this list is stale.
|
|
105
|
+
#
|
|
106
|
+
# The shared module is on BOTH examples' search paths, and that is a trap this check walks
|
|
107
|
+
# into unless it is told not to. A control id living in example/shared satisfies the grep
|
|
108
|
+
# for bare and expo alike — including for a host that never imports the module and still
|
|
109
|
+
# renders its own old screen. That is not hypothetical: on the day the shared module landed,
|
|
110
|
+
# `expo` reported "all 40 required controls present" while app/index.tsx was still building
|
|
111
|
+
# its own ScrollView out of useAmplyDemo. Green, and completely wrong.
|
|
112
|
+
#
|
|
113
|
+
# The native samples do not have this hole. Both Xcode projects list ../ios-shared/Sources
|
|
114
|
+
# and both Gradle modules include the shared srcDir, so on those platforms presence in the
|
|
115
|
+
# shared source IS compilation into the target. Metro resolving a package is not the same
|
|
116
|
+
# claim: a module on the search path is only rendered if something imports it.
|
|
117
|
+
#
|
|
118
|
+
# So each RN host must also prove it MOUNTS the shared module. `mount` below is a file that
|
|
119
|
+
# must exist and must import SHARED_PKG. Without this, the consolidation can be reported
|
|
120
|
+
# complete while half of it was never wired.
|
|
121
|
+
# ---------------------------------------------------------------------------------------
|
|
122
|
+
SHARED_PKG="@amply/sample-shared"
|
|
123
|
+
samples=(
|
|
124
|
+
"bare|example/bare/src example/shared|example/bare/src/App.tsx"
|
|
125
|
+
"expo|example/expo/app example/expo/src example/expo/components example/shared|example/expo/app/index.tsx"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
ATTR="testID"
|
|
129
|
+
INCLUDES="--include=*.ts --include=*.tsx --include=*.js --include=*.jsx"
|
|
130
|
+
|
|
131
|
+
fail=0
|
|
132
|
+
checked_total=0
|
|
133
|
+
|
|
134
|
+
echo "check-sample-parity: manifest revision $revision — $row_count rows"
|
|
135
|
+
echo " peer copy: $peer_state"
|
|
136
|
+
echo
|
|
137
|
+
|
|
138
|
+
for entry in "${samples[@]}"; do
|
|
139
|
+
name="${entry%%|*}"
|
|
140
|
+
rest="${entry#*|}"
|
|
141
|
+
roots="${rest%%|*}"
|
|
142
|
+
mount="${rest#*|}"
|
|
143
|
+
|
|
144
|
+
existing_roots=""
|
|
145
|
+
for r in $roots; do
|
|
146
|
+
[ -d "$r" ] && existing_roots="$existing_roots $r"
|
|
147
|
+
done
|
|
148
|
+
|
|
149
|
+
if [ -z "$existing_roots" ]; then
|
|
150
|
+
echo "check-sample-parity: FAIL — example '$name' has no source root on disk (looked in: $roots)" >&2
|
|
151
|
+
echo " the sample list in this script is stale." >&2
|
|
152
|
+
fail=1
|
|
153
|
+
continue
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Does this host actually MOUNT the shared module, or does it merely have it on a search
|
|
157
|
+
# path? Only the second is true of a host still rendering its own screen, and the id grep
|
|
158
|
+
# below cannot tell the difference — every shared id would satisfy it either way.
|
|
159
|
+
if [ -d "example/shared" ]; then
|
|
160
|
+
if [ ! -f "$mount" ]; then
|
|
161
|
+
echo "check-sample-parity: FAIL — example '$name' has no entry point at $mount" >&2
|
|
162
|
+
echo " the mount path in this script is stale." >&2
|
|
163
|
+
fail=1
|
|
164
|
+
continue
|
|
165
|
+
fi
|
|
166
|
+
if ! grep -qF "$SHARED_PKG" "$mount"; then
|
|
167
|
+
echo "check-sample-parity: FAIL — example '$name' does not mount the shared UI module." >&2
|
|
168
|
+
echo " $mount never imports $SHARED_PKG, so this example still" >&2
|
|
169
|
+
echo " renders its own screen. Every control id below would be" >&2
|
|
170
|
+
echo " satisfied by example/shared regardless — a green that means" >&2
|
|
171
|
+
echo " nothing. Mount the shared module in $mount." >&2
|
|
172
|
+
fail=1
|
|
173
|
+
continue
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
missing=()
|
|
178
|
+
unbound=()
|
|
179
|
+
required=0
|
|
180
|
+
|
|
181
|
+
while IFS=$'\t' read -r id android ios rn note; do
|
|
182
|
+
[ "$rn" = "x" ] || continue
|
|
183
|
+
required=$((required + 1))
|
|
184
|
+
|
|
185
|
+
id_esc=$(printf '%s' "$id" | sed 's/[.[\*^$()+?{}|\\]/\\&/g')
|
|
186
|
+
|
|
187
|
+
case "$note" in
|
|
188
|
+
launcharg:*)
|
|
189
|
+
# Not a UI control — the launch key just has to appear in the source. On RN the
|
|
190
|
+
# transport is a deeplink query param rather than a launch argument (neither
|
|
191
|
+
# example can read one without a new native dependency); the key spelling is the
|
|
192
|
+
# same, which is why one manifest row covers all three platforms.
|
|
193
|
+
# shellcheck disable=SC2086
|
|
194
|
+
if ! grep -RIlE $INCLUDES -e "$id_esc" $existing_roots > /dev/null 2>&1; then
|
|
195
|
+
missing+=("$id")
|
|
196
|
+
fi
|
|
197
|
+
continue
|
|
198
|
+
;;
|
|
199
|
+
prefix:*) pattern="${Q}${id_esc}\\." ;;
|
|
200
|
+
*) pattern="${Q}${id_esc}${Q}" ;;
|
|
201
|
+
esac
|
|
202
|
+
|
|
203
|
+
# shellcheck disable=SC2086
|
|
204
|
+
hits=$(grep -RInE $INCLUDES -e "$pattern" $existing_roots 2>/dev/null)
|
|
205
|
+
if [ -z "$hits" ]; then
|
|
206
|
+
missing+=("$id")
|
|
207
|
+
continue
|
|
208
|
+
fi
|
|
209
|
+
if ! printf '%s\n' "$hits" | grep -q "$ATTR"; then
|
|
210
|
+
unbound+=("$id")
|
|
211
|
+
fi
|
|
212
|
+
done <<< "$rows"
|
|
213
|
+
|
|
214
|
+
checked_total=$((checked_total + required))
|
|
215
|
+
|
|
216
|
+
if [ ${#missing[@]} -eq 0 ] && [ ${#unbound[@]} -eq 0 ]; then
|
|
217
|
+
echo " OK $name (rn) — all $required required controls present"
|
|
218
|
+
continue
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
fail=1
|
|
222
|
+
echo " FAIL $name (rn) — $((${#missing[@]} + ${#unbound[@]})) of $required required controls not satisfied"
|
|
223
|
+
echo " searched:$existing_roots"
|
|
224
|
+
if [ ${#missing[@]} -gt 0 ]; then
|
|
225
|
+
echo " missing (${#missing[@]}):"
|
|
226
|
+
for id in "${missing[@]}"; do echo " - $id"; done
|
|
227
|
+
fi
|
|
228
|
+
if [ ${#unbound[@]} -gt 0 ]; then
|
|
229
|
+
echo " present but not on a line with \`$ATTR\` (${#unbound[@]}):"
|
|
230
|
+
for id in "${unbound[@]}"; do echo " - $id"; done
|
|
231
|
+
fi
|
|
232
|
+
echo
|
|
233
|
+
done
|
|
234
|
+
|
|
235
|
+
# ---------------------------------------------------------------------------------------
|
|
236
|
+
# Retired ids: controls that were required once and must NOT come back. A presence-only
|
|
237
|
+
# check cannot see a control that should have been deleted, so without this a removal is
|
|
238
|
+
# held together by trust. Failed on sight, wherever they appear.
|
|
239
|
+
# ---------------------------------------------------------------------------------------
|
|
240
|
+
retired_ids=$(grep -E '^# +retired: +amply\.' "$MANIFEST" | sed -E 's/^# +retired: +([^ ]+).*/\1/')
|
|
241
|
+
for rid in $retired_ids; do
|
|
242
|
+
rid_esc=$(printf '%s' "$rid" | sed 's/[.[\*^$()+?{}|\\]/\\&/g')
|
|
243
|
+
# shellcheck disable=SC2086
|
|
244
|
+
retired_roots=""
|
|
245
|
+
for r in example/bare/src example/expo/app example/expo/src example/expo/components example/shared; do [ -d "$r" ] && retired_roots="$retired_roots $r"; done
|
|
246
|
+
# shellcheck disable=SC2086
|
|
247
|
+
hits=$(grep -RIlE --include=*.ts --include=*.tsx --include=*.js --include=*.jsx -e "${Q}${rid_esc}${Q}" $retired_roots 2>/dev/null || true)
|
|
248
|
+
if [ -n "$hits" ]; then
|
|
249
|
+
echo "check-sample-parity: FAIL — retired control '$rid' is still in the source:" >&2
|
|
250
|
+
printf ' %s\n' $hits >&2
|
|
251
|
+
echo " It was deliberately removed; see the manifest's Retired block." >&2
|
|
252
|
+
fail=1
|
|
253
|
+
fi
|
|
254
|
+
done
|
|
255
|
+
|
|
256
|
+
if [ "$fail" -ne 0 ]; then
|
|
257
|
+
echo "check-sample-parity: FAIL" >&2
|
|
258
|
+
echo " Each missing id is a control $SPEC_PATH requires and the example" >&2
|
|
259
|
+
echo " does not have. Tag the real widget with it: testID=\"<id>\"." >&2
|
|
260
|
+
exit 1
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
echo "check-sample-parity: OK — manifest revision $revision, $checked_total control checks across ${#samples[@]} examples."
|
|
@@ -221,7 +221,7 @@ describe('gate registration lifecycle', () => {
|
|
|
221
221
|
expect(mockNativeModule.registerGate).not.toHaveBeenCalled();
|
|
222
222
|
|
|
223
223
|
mockNativeModule.isInitialized.mockReturnValue(true);
|
|
224
|
-
await Amply.initialize({
|
|
224
|
+
await Amply.initialize({appId: 'a', apiKeyPublic: 'k', apiKeySecret: 's'});
|
|
225
225
|
|
|
226
226
|
expect(mockNativeModule.registerGate).toHaveBeenCalledWith('stillframe://ad', 'cancel', 0);
|
|
227
227
|
|
|
@@ -237,7 +237,7 @@ describe('gate registration lifecycle', () => {
|
|
|
237
237
|
expect(mockNativeModule.registerDeepLinkListener).not.toHaveBeenCalled();
|
|
238
238
|
|
|
239
239
|
mockNativeModule.isInitialized.mockReturnValue(true);
|
|
240
|
-
await Amply.initialize({
|
|
240
|
+
await Amply.initialize({appId: 'a', apiKeyPublic: 'k', apiKeySecret: 's'});
|
|
241
241
|
|
|
242
242
|
expect(mockNativeModule.registerDeepLinkListener).toHaveBeenCalledTimes(1);
|
|
243
243
|
});
|
|
@@ -257,7 +257,7 @@ describe('gate registration lifecycle', () => {
|
|
|
257
257
|
await Amply.registerGate('stillframe://ad', second);
|
|
258
258
|
|
|
259
259
|
mockNativeModule.isInitialized.mockReturnValue(true);
|
|
260
|
-
await Amply.initialize({
|
|
260
|
+
await Amply.initialize({appId: 'a', apiKeyPublic: 'k', apiKeySecret: 's'});
|
|
261
261
|
|
|
262
262
|
emitPresentation();
|
|
263
263
|
await flushMicrotasks();
|
|
@@ -273,7 +273,7 @@ describe('gate registration lifecycle', () => {
|
|
|
273
273
|
unsubscribe();
|
|
274
274
|
|
|
275
275
|
mockNativeModule.isInitialized.mockReturnValue(true);
|
|
276
|
-
await Amply.initialize({
|
|
276
|
+
await Amply.initialize({appId: 'a', apiKeyPublic: 'k', apiKeySecret: 's'});
|
|
277
277
|
|
|
278
278
|
expect(mockNativeModule.registerGate).not.toHaveBeenCalled();
|
|
279
279
|
});
|
|
@@ -29,17 +29,43 @@ describe('Amply JS API', () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('initializes the native module', async () => {
|
|
32
|
-
await Amply.initialize({ appId: 'id', apiKeyPublic: 'public' });
|
|
32
|
+
await Amply.initialize({ appId: 'id', apiKeyPublic: 'public', apiKeySecret: 'secret' });
|
|
33
33
|
|
|
34
34
|
expect(mockNativeModule.initialize).toHaveBeenCalledWith(
|
|
35
35
|
expect.objectContaining({ appId: 'id', apiKeyPublic: 'public' })
|
|
36
36
|
);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// A missing key used to reach the native SDK, whose config builder threw an exception that
|
|
40
|
+
// crossed into Objective-C uncaught and aborted the host app — no JS try/catch could stop it.
|
|
41
|
+
// Bad keys must cost the app its Amply, never its process.
|
|
42
|
+
describe.each([
|
|
43
|
+
['apiKeySecret', { appId: 'id', apiKeyPublic: 'public' }],
|
|
44
|
+
['apiKeySecret', { appId: 'id', apiKeyPublic: 'public', apiKeySecret: '' }],
|
|
45
|
+
['apiKeySecret', { appId: 'id', apiKeyPublic: 'public', apiKeySecret: ' ' }],
|
|
46
|
+
['apiKeySecret', { appId: 'id', apiKeyPublic: 'public', apiKeySecret: null }],
|
|
47
|
+
['apiKeyPublic', { appId: 'id', apiKeyPublic: '', apiKeySecret: 'secret' }],
|
|
48
|
+
['appId', { appId: '', apiKeyPublic: 'public', apiKeySecret: 'secret' }],
|
|
49
|
+
])('when %s is missing (%j)', (field, config) => {
|
|
50
|
+
it('rejects with AMP_INVALID_CONFIG, names the field and never calls the native module', async () => {
|
|
51
|
+
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
52
|
+
|
|
53
|
+
await expect(Amply.initialize(config as never)).rejects.toMatchObject({
|
|
54
|
+
code: 'AMP_INVALID_CONFIG',
|
|
55
|
+
message: expect.stringContaining(field),
|
|
56
|
+
});
|
|
57
|
+
expect(mockNativeModule.initialize).not.toHaveBeenCalled();
|
|
58
|
+
expect(error).toHaveBeenCalledWith(expect.stringContaining(field));
|
|
59
|
+
|
|
60
|
+
error.mockRestore();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
39
64
|
it('passes the base-URL overrides through to the native layer', async () => {
|
|
40
65
|
await Amply.initialize({
|
|
41
66
|
appId: 'id',
|
|
42
67
|
apiKeyPublic: 'public',
|
|
68
|
+
apiKeySecret: 'secret',
|
|
43
69
|
configBaseUrl: 'https://config-staging.example.com',
|
|
44
70
|
backendBaseUrl: 'https://api-staging.example.com',
|
|
45
71
|
});
|
|
@@ -59,6 +85,7 @@ describe('Amply JS API', () => {
|
|
|
59
85
|
await Amply.initialize({
|
|
60
86
|
appId: 'id',
|
|
61
87
|
apiKeyPublic: 'public',
|
|
88
|
+
apiKeySecret: 'secret',
|
|
62
89
|
endpoint: 'https://api-staging.example.com',
|
|
63
90
|
});
|
|
64
91
|
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type {FormatOptions} from './systemEventUtils';
|
|
|
6
6
|
import type {
|
|
7
7
|
CampaignResult,
|
|
8
8
|
GateDecision,
|
|
9
|
-
AmplyInitializationConfig,
|
|
9
|
+
AmplyInitializationConfig as NativeInitializationConfig,
|
|
10
10
|
DataSetSnapshot,
|
|
11
11
|
DataSetType,
|
|
12
12
|
DeepLinkEvent,
|
|
@@ -16,6 +16,36 @@ import type {
|
|
|
16
16
|
TrackEventPayload,
|
|
17
17
|
} from './nativeSpecs/NativeAmplyModule';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The keys from your application's page in Amply. All three are required: the SDK signs every
|
|
21
|
+
* request with `apiKeySecret` on the device, so the secret ships inside the app next to the
|
|
22
|
+
* public key. The codegen spec keeps the field optional only so the generated native structs
|
|
23
|
+
* stay unchanged; this is the contract callers see.
|
|
24
|
+
*/
|
|
25
|
+
export type AmplyInitializationConfig = Omit<NativeInitializationConfig, 'apiKeySecret'> & {
|
|
26
|
+
apiKeySecret: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const REQUIRED_KEYS = ['appId', 'apiKeyPublic', 'apiKeySecret'] as const;
|
|
30
|
+
|
|
31
|
+
/** Rejection raised by {@link initialize} for a config the SDK cannot start with. */
|
|
32
|
+
export class AmplyConfigError extends Error {
|
|
33
|
+
readonly code = 'AMP_INVALID_CONFIG';
|
|
34
|
+
|
|
35
|
+
constructor(message: string) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'AmplyConfigError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The first required key that is missing or blank, if any. */
|
|
42
|
+
function missingKey(config: Partial<Record<(typeof REQUIRED_KEYS)[number], unknown>>): string | undefined {
|
|
43
|
+
return REQUIRED_KEYS.find((key) => {
|
|
44
|
+
const value = config?.[key];
|
|
45
|
+
return typeof value !== 'string' || value.trim().length === 0;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
let deepLinkRegistered = false;
|
|
20
50
|
let debugLogListenerRegistered = false;
|
|
21
51
|
const deepLinkSubscriptions = new Set<() => void>();
|
|
@@ -189,6 +219,15 @@ function trackDeepLinkSubscription(subscription?: {remove?: () => void}): () =>
|
|
|
189
219
|
}
|
|
190
220
|
|
|
191
221
|
export async function initialize(config: AmplyInitializationConfig): Promise<void> {
|
|
222
|
+
// Checked here, before the native module sees anything: a missing key used to reach the
|
|
223
|
+
// native config builder, which threw across the Objective-C boundary and aborted the host
|
|
224
|
+
// app. Bad keys cost the app its Amply — never its process. The SDK stays uninitialized.
|
|
225
|
+
const missing = missingKey(config);
|
|
226
|
+
if (missing) {
|
|
227
|
+
const message = `[Amply] '${missing}' is required — Amply is not initialized. Copy it from your application's page in Amply.`;
|
|
228
|
+
console.error(message);
|
|
229
|
+
throw new AmplyConfigError(message);
|
|
230
|
+
}
|
|
192
231
|
// Set up debug log listener if debug mode is enabled
|
|
193
232
|
if (config.debug || config.logLevel) {
|
|
194
233
|
ensureDebugLogListener();
|
|
@@ -570,7 +609,6 @@ export function removeAllListeners(): void {
|
|
|
570
609
|
}
|
|
571
610
|
|
|
572
611
|
export type {
|
|
573
|
-
AmplyInitializationConfig,
|
|
574
612
|
DataSetSnapshot,
|
|
575
613
|
DataSetType,
|
|
576
614
|
DeepLinkEvent,
|