adwhale-sdk-react-native 2.7.200 → 2.7.202

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 (3) hide show
  1. package/README.md +3 -3
  2. package/package.json +3 -1
  3. package/plugin/index.js +326 -104
package/README.md CHANGED
@@ -43,7 +43,7 @@ npm install adwhale-sdk-react-native
43
43
  운영 안정성을 중시하는 경우
44
44
  ```json
45
45
  {
46
- "adwhale-sdk-react-native": "~2.7.200"
46
+ "adwhale-sdk-react-native": "~2.7.202"
47
47
  }
48
48
  ```
49
49
  - Android SDK 2.7.2 기반 유지
@@ -52,7 +52,7 @@ npm install adwhale-sdk-react-native
52
52
  ### 동일한 Android SDK 기반의 최신 RN 수정만 사용
53
53
  ```json
54
54
  {
55
- "adwhale-sdk-react-native": "^2.7.200"
55
+ "adwhale-sdk-react-native": "^2.7.202"
56
56
  }
57
57
  ```
58
58
  - Android SDK 2.7.x 범위 내 최신 RN 수정 자동 반영
@@ -62,7 +62,7 @@ npm install adwhale-sdk-react-native
62
62
  ### 특정 버전을 완전히 고정하여 사용하는 경우
63
63
  ```json
64
64
  {
65
- "adwhale-sdk-react-native": "2.7.200"
65
+ "adwhale-sdk-react-native": "2.7.202"
66
66
  }
67
67
  ```
68
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adwhale-sdk-react-native",
3
- "version": "2.7.200",
3
+ "version": "2.7.202",
4
4
  "description": "Adwhale SDK React Native library for integrating Adwhale advertising mediation SDK into React Native applications.",
5
5
  "main": "./lib/module/index.js",
6
6
  "react-native": "./lib/module/index.js",
@@ -12,6 +12,8 @@
12
12
  "source": "./src/index.ts",
13
13
  "default": "./lib/module/index.js"
14
14
  },
15
+ "./app.plugin.js": "./app.plugin.js",
16
+ "./plugin": "./plugin/index.js",
15
17
  "./package.json": "./package.json"
16
18
  },
17
19
  "files": [
package/plugin/index.js CHANGED
@@ -1,8 +1,41 @@
1
- // plugin/index.js
2
- const { createRunOncePlugin, withAndroidBuildGradle } = require('@expo/config-plugins');
1
+ /**
2
+ * AdWhale Expo Config Plugin (Most Compatible)
3
+ *
4
+ * 목적:
5
+ * - Expo Bare / Managed / prebuild / EAS Dev Client 환경에서
6
+ * AdWhale Android SDK 의존성을 정상적으로 resolve 하기 위해
7
+ * Maven Repository들을 자동으로 Gradle 설정에 추가한다.
8
+ *
9
+ * 왜 withDangerousMod?
10
+ * - 일부 Expo/@expo config-plugins 버전에서는 Android 전용 helper(mod)
11
+ * (예: withAndroidBuildGradle, withAndroidProjectBuildGradle)가 존재하지 않는다.
12
+ * - helper에 의존하면 TypeError가 발생할 수 있다.
13
+ * - withDangerousMod는 폭넓게 제공되며, 파일을 직접 수정하는 방식이라 호환성이 가장 높다.
14
+ *
15
+ * 적용 정책:
16
+ * 1) android/settings.gradle
17
+ * - rootProject.name 바로 다음 줄에
18
+ * dependencyResolutionManagement { repositories { ... } } 추가/보강
19
+ *
20
+ * 2) android/build.gradle (project-level)
21
+ * - allprojects { repositories { ... } } 안에만 Maven repo 추가
22
+ * - buildscript.repositories에는 절대 추가하지 않음
23
+ *
24
+ * 3) 단, settings.gradle에
25
+ * repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
26
+ * 가 존재하면:
27
+ * - project-level repo 사용이 금지될 수 있으므로
28
+ * - build.gradle 수정은 생략
29
+ */
30
+
31
+ const fs = require('fs');
32
+ const path = require('path');
33
+ const { createRunOncePlugin, withDangerousMod } = require('expo/config-plugins');
3
34
  const pkg = require('../package.json');
4
35
 
5
- // Maven repository URLs to add
36
+ /**
37
+ * 추가할 Maven Repository 목록
38
+ */
6
39
  const MAVEN_REPOSITORIES = [
7
40
  {
8
41
  comment: 'AdWhale SDK Repository Public Access Info',
@@ -23,125 +56,314 @@ const MAVEN_REPOSITORIES = [
23
56
  ];
24
57
 
25
58
  /**
26
- * Generate maven repositories block content
59
+ * 중복 삽입 방지를 위한 마커
27
60
  */
28
- function generateMavenRepositories(indent = ' ') {
29
- return MAVEN_REPOSITORIES.map(
61
+ const MARKER_BEGIN = '// [AdWhale] BEGIN Maven Repositories';
62
+ const MARKER_END = '// [AdWhale] END Maven Repositories';
63
+
64
+ function readTextIfExists(filePath) {
65
+ if (!fs.existsSync(filePath)) return null;
66
+ return fs.readFileSync(filePath, 'utf8');
67
+ }
68
+
69
+ function writeText(filePath, contents) {
70
+ fs.writeFileSync(filePath, contents, 'utf8');
71
+ }
72
+
73
+ /**
74
+ * settings.gradle에 FAIL_ON_PROJECT_REPOS가 있으면
75
+ * project-level repo를 금지할 수 있으므로 build.gradle 수정은 생략한다.
76
+ */
77
+ function hasFailOnProjectRepos(contents) {
78
+ return /repositoriesMode\.set\s*\(\s*RepositoriesMode\.FAIL_ON_PROJECT_REPOS\s*\)/.test(
79
+ contents
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Maven repo 블록 생성 (마커 포함)
85
+ */
86
+ function generateReposBlock(indent = ' ') {
87
+ const body = MAVEN_REPOSITORIES.map(
30
88
  (repo) => `${indent}// ${repo.comment}\n${indent}maven { url "${repo.url}" }`
31
89
  ).join('\n\n');
90
+
91
+ return `${indent}${MARKER_BEGIN}\n\n${body}\n\n${indent}${MARKER_END}`;
32
92
  }
33
93
 
34
94
  /**
35
- * Add maven repositories to Android build.gradle
36
- * This modifies the allprojects { repositories { ... } } block
95
+ * repositories { ... } 내부에 google(), mavenCentral()이 없으면 추가해준다.
96
+ * (resolve 기본 repo 안전장치)
37
97
  */
38
- const withAdwhaleMavenRepositories = (config) => {
39
- return withAndroidBuildGradle(config, (config) => {
40
- let buildGradle = config.modResults.contents;
98
+ function ensureGoogleAndMavenCentral(reposBlock, indent = ' ') {
99
+ if (!/google\(\)/.test(reposBlock)) {
100
+ reposBlock = reposBlock.replace(/repositories\s*\{\s*/m, (m) => `${m}\n${indent}google()`);
101
+ }
41
102
 
42
- // Check if allprojects block exists
43
- if (!buildGradle.includes('allprojects')) {
44
- // If allprojects doesn't exist, add it at the end
45
- const repositoriesBlock = `
103
+ if (!/mavenCentral\(\)/.test(reposBlock)) {
104
+ if (/google\(\)/.test(reposBlock)) {
105
+ reposBlock = reposBlock.replace(/(google\(\))/m, `$1\n${indent}mavenCentral()`);
106
+ } else {
107
+ reposBlock = reposBlock.replace(
108
+ /repositories\s*\{\s*/m,
109
+ (m) => `${m}\n${indent}mavenCentral()`
110
+ );
111
+ }
112
+ }
46
113
 
47
- allprojects {
48
- repositories {
49
- google()
50
- mavenCentral()
114
+ return reposBlock;
115
+ }
51
116
 
52
- ${generateMavenRepositories()}
53
- }
54
- }`;
55
- config.modResults.contents = buildGradle + repositoriesBlock;
56
- return config;
57
- }
117
+ /**
118
+ * '{'의 매칭 '}' 범위를 찾아서 {start,end} 반환 (end inclusive)
119
+ * - 중첩 brace를 고려하여 깊이(depth)로 스캔
120
+ */
121
+ function extractBraceRange(text, openBraceIndex) {
122
+ if (openBraceIndex < 0 || openBraceIndex >= text.length) return null;
123
+ if (text[openBraceIndex] !== '{') return null;
58
124
 
59
- // allprojects exists, check if repositories block exists inside it
60
- const allprojectsRepositoriesRegex = /allprojects\s*\{[\s\S]*?repositories\s*\{/;
61
-
62
- if (!allprojectsRepositoriesRegex.test(buildGradle)) {
63
- // allprojects exists but no repositories block, add it
64
- const repositoriesBlock = ` repositories {
65
- google()
66
- mavenCentral()
125
+ let depth = 1;
126
+ let i = openBraceIndex;
67
127
 
68
- ${generateMavenRepositories(' ')}
128
+ while (i + 1 < text.length && depth > 0) {
129
+ i++;
130
+ const ch = text[i];
131
+ if (ch === '{') depth++;
132
+ else if (ch === '}') depth--;
133
+ }
69
134
 
70
- }`;
135
+ if (depth !== 0) return null;
136
+ return { start: openBraceIndex, end: i };
137
+ }
71
138
 
72
- // Insert repositories block after allprojects {
73
- buildGradle = buildGradle.replace(
74
- /(allprojects\s*\{)/,
75
- `$1\n${repositoriesBlock}`
76
- );
77
- config.modResults.contents = buildGradle;
78
- return config;
79
- }
139
+ /**
140
+ * android/settings.gradle 패치
141
+ *
142
+ * 목표:
143
+ * - rootProject.name 바로 다음 줄에 dependencyResolutionManagement 블록을 추가한다.
144
+ * - 이미 DRM이 존재하면:
145
+ * - repositories 블록이 없으면 추가
146
+ * - 있으면 missing repo만 추가
147
+ */
148
+ function patchSettingsGradle(contents) {
149
+ const failOnProjectRepos = hasFailOnProjectRepos(contents);
80
150
 
81
- // allprojects { repositories { ... } } exists, add missing repositories
82
- // Ensure google() exists in allprojects.repositories
83
- const googleInAllprojectsRegex = /allprojects\s*\{[\s\S]*?repositories\s*\{[\s\S]*?google\(\)/;
84
- if (!googleInAllprojectsRegex.test(buildGradle)) {
85
- buildGradle = buildGradle.replace(
86
- /(allprojects\s*\{[\s\S]*?repositories\s*\{)/,
87
- '$1\n google()'
88
- );
151
+ // 이미 적용되어 있으면 스킵
152
+ if (contents.includes(MARKER_BEGIN)) {
153
+ return { contents, failOnProjectRepos, changed: false };
154
+ }
155
+
156
+ // DRM 자체가 없으면 rootProject.name 다음에 삽입
157
+ if (!/dependencyResolutionManagement\s*\{/.test(contents)) {
158
+ const drmBlock =
159
+ `\n\ndependencyResolutionManagement {\n` +
160
+ ` repositories {\n` +
161
+ ` google()\n` +
162
+ ` mavenCentral()\n\n` +
163
+ `${generateReposBlock(' ')}\n` +
164
+ ` }\n` +
165
+ `}\n`;
166
+
167
+ const rootNameRegex = /(^\s*rootProject\.name\s*=\s*['"][^'"]+['"]\s*$)/m;
168
+ const match = contents.match(rootNameRegex);
169
+
170
+ if (match) {
171
+ const insertAt = match.index + match[0].length;
172
+ const newContents = contents.slice(0, insertAt) + drmBlock + contents.slice(insertAt);
173
+ return { contents: newContents, failOnProjectRepos, changed: true };
89
174
  }
90
175
 
91
- // Ensure mavenCentral() exists in allprojects.repositories
92
- const mavenCentralInAllprojectsRegex = /allprojects\s*\{[\s\S]*?repositories\s*\{[\s\S]*?mavenCentral\(\)/;
93
- if (!mavenCentralInAllprojectsRegex.test(buildGradle)) {
94
- if (googleInAllprojectsRegex.test(buildGradle)) {
95
- buildGradle = buildGradle.replace(
96
- /(allprojects\s*\{[\s\S]*?repositories\s*\{[\s\S]*?google\(\))/,
97
- '$1\n mavenCentral()'
98
- );
99
- } else {
100
- buildGradle = buildGradle.replace(
101
- /(allprojects\s*\{[\s\S]*?repositories\s*\{)/,
102
- '$1\n mavenCentral()'
103
- );
176
+ // rootProject.name이 없으면 파일 끝에 append
177
+ return { contents: contents + drmBlock, failOnProjectRepos, changed: true };
178
+ }
179
+
180
+ // DRM은 있으나 repositories 블록이 없으면 추가
181
+ const drmHasRepos = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/.test(
182
+ contents
183
+ );
184
+ if (!drmHasRepos) {
185
+ const newContents = contents.replace(
186
+ /(dependencyResolutionManagement\s*\{)/m,
187
+ `$1\n repositories {\n google()\n mavenCentral()\n\n${generateReposBlock(
188
+ ' '
189
+ )}\n }\n`
190
+ );
191
+ return { contents: newContents, failOnProjectRepos, changed: true };
192
+ }
193
+
194
+ // DRM.repositories 존재: missing repo만 추가
195
+ const missing = MAVEN_REPOSITORIES.filter((r) => !contents.includes(r.url));
196
+ if (missing.length === 0) {
197
+ return { contents, failOnProjectRepos, changed: false };
198
+ }
199
+
200
+ const indent = ' ';
201
+ const body = missing
202
+ .map((r) => `${indent}// ${r.comment}\n${indent}maven { url "${r.url}" }`)
203
+ .join('\n\n');
204
+ const block = `${indent}${MARKER_BEGIN}\n\n${body}\n\n${indent}${MARKER_END}`;
205
+
206
+ let newContents = contents;
207
+
208
+ // 가장 무난한 위치: repositories 내 mavenCentral() 다음
209
+ if (/mavenCentral\(\)/.test(newContents)) {
210
+ newContents = newContents.replace(/(mavenCentral\(\)\s*)/m, `$1\n\n${block}\n`);
211
+ } else {
212
+ // mavenCentral이 아예 없으면 repositories { 다음에 삽입
213
+ newContents = newContents.replace(/(repositories\s*\{\s*)/m, `$1\n${block}\n`);
214
+ }
215
+
216
+ return { contents: newContents, failOnProjectRepos, changed: true };
217
+ }
218
+
219
+ /**
220
+ * android/build.gradle(project-level) 패치
221
+ *
222
+ * 목표:
223
+ * - buildscript.repositories는 절대 건드리지 않는다.
224
+ * - allprojects { repositories { ... } } 블록 "안"에만 repo를 삽입한다.
225
+ *
226
+ * 삽입 위치 정책:
227
+ * - jitpack이 있으면: jitpack 다음 줄에 삽입 (원하는 형태)
228
+ * - jitpack이 없으면: repositories 블록 닫기 직전에 삽입
229
+ */
230
+ function patchProjectBuildGradle(contents) {
231
+ // 이미 적용되어 있으면 스킵
232
+ if (contents.includes(MARKER_BEGIN)) return { contents, changed: false };
233
+
234
+ // allprojects 없으면 append
235
+ if (!/allprojects\s*\{/.test(contents)) {
236
+ const block =
237
+ `\n\nallprojects {\n` +
238
+ ` repositories {\n` +
239
+ ` google()\n` +
240
+ ` mavenCentral()\n` +
241
+ ` maven { url 'https://www.jitpack.io' }\n\n` +
242
+ `${generateReposBlock(' ')}\n` +
243
+ ` }\n` +
244
+ `}\n`;
245
+ return { contents: contents + block, changed: true };
246
+ }
247
+
248
+ // allprojects 헤더와 '{' 찾기
249
+ const apHeader = contents.match(/allprojects\s*\{/m);
250
+ if (!apHeader) return { contents, changed: false };
251
+
252
+ const apOpen = apHeader.index + apHeader[0].length - 1; // '{'
253
+ const apRange = extractBraceRange(contents, apOpen);
254
+ if (!apRange) return { contents, changed: false };
255
+
256
+ const apText = contents.slice(apHeader.index, apRange.end + 1); // "allprojects { ... }"
257
+
258
+ // allprojects 안에 repositories가 없으면 삽입
259
+ if (!/repositories\s*\{/.test(apText)) {
260
+ const insertion =
261
+ `\n repositories {\n` +
262
+ ` google()\n` +
263
+ ` mavenCentral()\n` +
264
+ ` maven { url 'https://www.jitpack.io' }\n\n` +
265
+ `${generateReposBlock(' ')}\n` +
266
+ ` }\n`;
267
+
268
+ const newApText = apText.replace(/allprojects\s*\{\s*/m, (m) => m + insertion);
269
+
270
+ const newContents =
271
+ contents.slice(0, apHeader.index) + newApText + contents.slice(apRange.end + 1);
272
+
273
+ return { contents: newContents, changed: true };
274
+ }
275
+
276
+ // allprojects.repositories 헤더와 '{' 찾기 (apText 내부 인덱스)
277
+ const repoHeader = apText.match(/repositories\s*\{/m);
278
+ if (!repoHeader) return { contents, changed: false };
279
+
280
+ const repoOpenInAp = repoHeader.index + repoHeader[0].length - 1;
281
+ const repoRangeInAp = extractBraceRange(apText, repoOpenInAp);
282
+ if (!repoRangeInAp) return { contents, changed: false };
283
+
284
+ let repoBlockText = apText.slice(repoHeader.index, repoRangeInAp.end + 1); // "repositories { ... }"
285
+
286
+ // 안전장치: 기본 repo 보장
287
+ repoBlockText = ensureGoogleAndMavenCentral(repoBlockText, ' ');
288
+
289
+ // missing만 추가
290
+ const missing = MAVEN_REPOSITORIES.filter((r) => !repoBlockText.includes(r.url));
291
+ if (missing.length === 0) return { contents, changed: false };
292
+
293
+ const indent = ' ';
294
+ const body = missing
295
+ .map((r) => `${indent}// ${r.comment}\n${indent}maven { url "${r.url}" }`)
296
+ .join('\n\n');
297
+ const markerBlock = `${indent}${MARKER_BEGIN}\n\n${body}\n\n${indent}${MARKER_END}`;
298
+
299
+ const jitpackRegex = /maven\s*\{\s*url\s*['"]https:\/\/www\.jitpack\.io['"]\s*\}/m;
300
+
301
+ if (jitpackRegex.test(repoBlockText)) {
302
+ // jitpack 다음에 삽입
303
+ repoBlockText = repoBlockText.replace(jitpackRegex, (m) => `${m}\n\n${markerBlock}`);
304
+ } else {
305
+ // repositories 닫기 직전에 삽입
306
+ repoBlockText = repoBlockText.replace(/\}\s*$/m, `\n\n${markerBlock}\n}`);
307
+ }
308
+
309
+ // apText 안에서 repositories 블록 교체
310
+ const newApText =
311
+ apText.slice(0, repoHeader.index) + repoBlockText + apText.slice(repoRangeInAp.end + 1);
312
+
313
+ // 전체 파일에서 allprojects 블록 교체
314
+ const newContents =
315
+ contents.slice(0, apHeader.index) + newApText + contents.slice(apRange.end + 1);
316
+
317
+ return { contents: newContents, changed: true };
318
+ }
319
+
320
+ /**
321
+ * 실제 적용 플러그인
322
+ *
323
+ * - android 폴더가 생성된 이후(prebuild) 시점에 실행되며,
324
+ * - settings.gradle 먼저 수정하여 정책을 판단하고,
325
+ * - FAIL_ON_PROJECT_REPOS가 아니면 build.gradle도 수정한다.
326
+ */
327
+ function withAdwhaleMavenRepositories(config) {
328
+ return withDangerousMod(config, [
329
+ 'android',
330
+ async (config) => {
331
+ const projectRoot = config.modRequest.projectRoot;
332
+ const androidDir = path.join(projectRoot, 'android');
333
+
334
+ const settingsGradlePath = path.join(androidDir, 'settings.gradle');
335
+ const buildGradlePath = path.join(androidDir, 'build.gradle');
336
+
337
+ // 1) settings.gradle 패치
338
+ const settingsText = readTextIfExists(settingsGradlePath);
339
+ let failOnProjectRepos = false;
340
+
341
+ if (settingsText) {
342
+ const res = patchSettingsGradle(settingsText);
343
+ failOnProjectRepos = res.failOnProjectRepos;
344
+
345
+ if (res.changed) {
346
+ writeText(settingsGradlePath, res.contents);
347
+ }
104
348
  }
105
- }
106
349
 
107
- // Add each maven repository if it doesn't exist
108
- MAVEN_REPOSITORIES.forEach((repo) => {
109
- const urlEscaped = repo.url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
110
- const urlInAllprojectsRegex = new RegExp(
111
- `allprojects\\s*\\{[\\s\\S]*?repositories\\s*\\{[\\s\\S]*?${urlEscaped}`
112
- );
113
-
114
- if (!urlInAllprojectsRegex.test(buildGradle)) {
115
- // Add after mavenCentral() if it exists in allprojects.repositories
116
- if (mavenCentralInAllprojectsRegex.test(buildGradle)) {
117
- buildGradle = buildGradle.replace(
118
- /(allprojects\s*\{[\s\S]*?repositories\s*\{[\s\S]*?mavenCentral\(\))/,
119
- `$1\n\n // ${repo.comment}\n maven { url "${repo.url}" }`
120
- );
121
- } else {
122
- // Add after repositories {
123
- buildGradle = buildGradle.replace(
124
- /(allprojects\s*\{[\s\S]*?repositories\s*\{)/,
125
- `$1\n // ${repo.comment}\n maven { url "${repo.url}" }`
126
- );
350
+ // 2) FAIL_ON_PROJECT_REPOS이면 build.gradle 수정 생략
351
+ if (failOnProjectRepos) {
352
+ return config;
353
+ }
354
+
355
+ // 3) build.gradle(project) 패치
356
+ const buildText = readTextIfExists(buildGradlePath);
357
+ if (buildText) {
358
+ const res2 = patchProjectBuildGradle(buildText);
359
+ if (res2.changed) {
360
+ writeText(buildGradlePath, res2.contents);
127
361
  }
128
362
  }
129
- });
130
-
131
- config.modResults.contents = buildGradle;
132
- return config;
133
- });
134
- };
135
-
136
- // Expo config plugin
137
- const withAdwhaleSdkReactNative = (config, props = {}) => {
138
- // Android build.gradle에 maven repositories 추가
139
- config = withAdwhaleMavenRepositories(config);
140
- return config;
141
- };
142
-
143
- module.exports = createRunOncePlugin(
144
- withAdwhaleSdkReactNative,
145
- pkg.name,
146
- pkg.version
147
- );
363
+
364
+ return config;
365
+ },
366
+ ]);
367
+ }
368
+
369
+ module.exports = createRunOncePlugin(withAdwhaleMavenRepositories, pkg.name, pkg.version);