@goliapkg/sentori-expo 7.0.1 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.plugin.js +156 -25
- package/package.json +5 -5
package/app.plugin.js
CHANGED
|
@@ -39,10 +39,19 @@ const {
|
|
|
39
39
|
withProjectBuildGradle,
|
|
40
40
|
withAppBuildGradle,
|
|
41
41
|
withDangerousMod,
|
|
42
|
+
withXcodeProject,
|
|
42
43
|
AndroidConfig,
|
|
43
44
|
withPlugins,
|
|
44
45
|
} = require('@expo/config-plugins')
|
|
45
46
|
|
|
47
|
+
// NSE target wiring constants. Path is relative to the .xcodeproj — the
|
|
48
|
+
// `xcode` package's addBuildPhase resolves filePaths against the project
|
|
49
|
+
// root, not the target subfolder, so the NSE/ prefix is required here
|
|
50
|
+
// even though the source actually lives at ios/SentoriNSE/<basename>.
|
|
51
|
+
const NSE_TARGET = 'SentoriNSE'
|
|
52
|
+
const NSE_SOURCE_REL = 'SentoriNSE/SentoriNotificationServiceExtension.swift'
|
|
53
|
+
const NSE_PLIST_REL = 'SentoriNSE-Info.plist'
|
|
54
|
+
|
|
46
55
|
const SENTORI_VERSION_KEY = 'SentoriSdkVersion'
|
|
47
56
|
const FIREBASE_BOM_VERSION = '33.5.1'
|
|
48
57
|
const GOOGLE_SERVICES_VERSION = '4.4.2'
|
|
@@ -172,7 +181,7 @@ const withSentoriGoogleServicesJson = (config, props = {}) => {
|
|
|
172
181
|
])
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
// ── v2.28 iOS Notification Service Extension
|
|
184
|
+
// ── v2.28 iOS Notification Service Extension ─────────────────────
|
|
176
185
|
//
|
|
177
186
|
// Rich-media notifications (images / future video) require an NSE
|
|
178
187
|
// target on the iOS app. The Sentori NSE template downloads the URL
|
|
@@ -180,14 +189,48 @@ const withSentoriGoogleServicesJson = (config, props = {}) => {
|
|
|
180
189
|
// displays the notification. APNs server side sets this key when
|
|
181
190
|
// `richMedia.imageUrl` is on the send (v2.28+).
|
|
182
191
|
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
192
|
+
// As of 7.0.2 the wiring is fully automated across two plugins:
|
|
193
|
+
//
|
|
194
|
+
// - withSentoriNSE (this section) writes the template files
|
|
195
|
+
// into ios/SentoriNSE/ AND syncs the NSE
|
|
196
|
+
// Info.plist's CFBundleShortVersionString /
|
|
197
|
+
// CFBundleVersion to the host app's values.
|
|
198
|
+
// Apple's app-extension verifier rejects an
|
|
199
|
+
// .appex whose version keys do not match
|
|
200
|
+
// the parent app at signing time, so the
|
|
201
|
+
// sync is mandatory.
|
|
202
|
+
//
|
|
203
|
+
// - withSentoriNSETarget (further down) creates the actual Xcode
|
|
204
|
+
// target via withXcodeProject so signing +
|
|
205
|
+
// building succeed without any manual
|
|
206
|
+
// Xcode-UI step.
|
|
188
207
|
//
|
|
189
|
-
// Opt out with `{ ios: false }` (which
|
|
190
|
-
//
|
|
208
|
+
// Opt out with `{ ios: false }` (which drops the rest of the iOS push
|
|
209
|
+
// wiring too) or with `{ nse: false }` for just NSE (template + target).
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Rewrite the NSE Info.plist's marketing + build version strings to
|
|
213
|
+
* track the host app. Apple rejects an .appex at signing time when its
|
|
214
|
+
* CFBundleShortVersionString / CFBundleVersion don't match the parent
|
|
215
|
+
* app — present in every Sentori install that enabled NSE before 7.0.2.
|
|
216
|
+
*
|
|
217
|
+
* Pure / exported for unit-test coverage.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} contents — current NSE-Info.plist contents
|
|
220
|
+
* @param {{ version: string, buildNumber: string }} ver
|
|
221
|
+
* @returns {string}
|
|
222
|
+
*/
|
|
223
|
+
function syncNSEPlistVersion(contents, ver) {
|
|
224
|
+
return contents
|
|
225
|
+
.replace(
|
|
226
|
+
/(<key>CFBundleShortVersionString<\/key>\s*<string>)[^<]*(<\/string>)/,
|
|
227
|
+
`$1${ver.version}$2`
|
|
228
|
+
)
|
|
229
|
+
.replace(
|
|
230
|
+
/(<key>CFBundleVersion<\/key>\s*<string>)[^<]*(<\/string>)/,
|
|
231
|
+
`$1${ver.buildNumber}$2`
|
|
232
|
+
)
|
|
233
|
+
}
|
|
191
234
|
|
|
192
235
|
/**
|
|
193
236
|
* @param {import('@expo/config-plugins').ExpoConfig} config
|
|
@@ -197,7 +240,7 @@ const withSentoriNSE = (config) => {
|
|
|
197
240
|
'ios',
|
|
198
241
|
async (cfg) => {
|
|
199
242
|
const platformRoot = cfg.modRequest.platformProjectRoot
|
|
200
|
-
const destDir = path.join(platformRoot,
|
|
243
|
+
const destDir = path.join(platformRoot, NSE_TARGET)
|
|
201
244
|
const templateDir = path.join(__dirname, 'templates', 'ios-nse')
|
|
202
245
|
const swiftSrc = path.join(templateDir, 'SentoriNotificationServiceExtension.swift')
|
|
203
246
|
const plistSrc = path.join(templateDir, 'SentoriNSE-Info.plist')
|
|
@@ -213,27 +256,109 @@ const withSentoriNSE = (config) => {
|
|
|
213
256
|
swiftSrc,
|
|
214
257
|
path.join(destDir, 'SentoriNotificationServiceExtension.swift')
|
|
215
258
|
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
259
|
+
const plistDest = path.join(destDir, NSE_PLIST_REL)
|
|
260
|
+
fs.copyFileSync(plistSrc, plistDest)
|
|
261
|
+
|
|
262
|
+
// Version-sync the freshly-copied plist in the same callback so
|
|
263
|
+
// the read+write is atomic with the copy (no dependency on
|
|
264
|
+
// Expo's cross-plugin dangerousMod ordering, which is LIFO and
|
|
265
|
+
// therefore brittle to reason about). Expo defaults the host
|
|
266
|
+
// app's CFBundleVersion to '1' when ios.buildNumber is unset.
|
|
267
|
+
const version = cfg.version
|
|
268
|
+
const buildNumber = cfg.ios?.buildNumber ?? '1'
|
|
269
|
+
if (version) {
|
|
270
|
+
const current = fs.readFileSync(plistDest, 'utf-8')
|
|
271
|
+
const updated = syncNSEPlistVersion(current, { buildNumber, version })
|
|
272
|
+
if (updated !== current) fs.writeFileSync(plistDest, updated)
|
|
231
273
|
}
|
|
232
274
|
return cfg
|
|
233
275
|
},
|
|
234
276
|
])
|
|
235
277
|
}
|
|
236
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Pure pbxproj mutation. Adds the NSE target + build phases + build
|
|
281
|
+
* settings, or returns false when the target already exists.
|
|
282
|
+
*
|
|
283
|
+
* Exported for unit-test coverage.
|
|
284
|
+
*
|
|
285
|
+
* @param {object} pbxproj — `xcode` package's Pbxproj instance
|
|
286
|
+
* (cfg.modResults inside withXcodeProject)
|
|
287
|
+
* @param {{ mainBundleId: string, deploymentTarget: string }} opts
|
|
288
|
+
* @returns {boolean} — true when the target was added, false when no-op
|
|
289
|
+
*/
|
|
290
|
+
function injectNSETarget(pbxproj, opts) {
|
|
291
|
+
if (pbxproj.pbxTargetByName(NSE_TARGET)) return false
|
|
292
|
+
|
|
293
|
+
const { deploymentTarget, mainBundleId } = opts
|
|
294
|
+
if (!mainBundleId) throw new Error('[sentori-expo] mainBundleId is required for NSE target')
|
|
295
|
+
if (!deploymentTarget) {
|
|
296
|
+
throw new Error('[sentori-expo] deploymentTarget is required for NSE target')
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// addTarget creates the PBXNativeTarget + XCBuildConfigurationList with
|
|
300
|
+
// INFOPLIST_FILE / PRODUCT_NAME / SKIP_INSTALL pre-set, and wires the
|
|
301
|
+
// produced .appex into a "Copy Files" phase on the main app target —
|
|
302
|
+
// that is Xcode's "Embed App Extensions" phase under a different name.
|
|
303
|
+
const target = pbxproj.addTarget(
|
|
304
|
+
NSE_TARGET,
|
|
305
|
+
'app_extension',
|
|
306
|
+
NSE_TARGET,
|
|
307
|
+
`${mainBundleId}.${NSE_TARGET}`
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
pbxproj.addBuildPhase([NSE_SOURCE_REL], 'PBXSourcesBuildPhase', 'Sources', target.uuid)
|
|
311
|
+
pbxproj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid)
|
|
312
|
+
pbxproj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid)
|
|
313
|
+
|
|
314
|
+
// xcode 3.0.x stores native target names quoted (`'"SentoriNSE"'`), so
|
|
315
|
+
// `pbxTargetByName('SentoriNSE')` / `updateBuildProperty(..., 'SentoriNSE')`
|
|
316
|
+
// miss the target via string equality. Patch the buildSettings dictionary
|
|
317
|
+
// for the NSE build configurations directly off the project hash.
|
|
318
|
+
const settings = {
|
|
319
|
+
CLANG_ENABLE_MODULES: 'YES',
|
|
320
|
+
CODE_SIGN_STYLE: 'Automatic',
|
|
321
|
+
IPHONEOS_DEPLOYMENT_TARGET: deploymentTarget,
|
|
322
|
+
SWIFT_VERSION: '5.0',
|
|
323
|
+
TARGETED_DEVICE_FAMILY: '"1,2"',
|
|
324
|
+
}
|
|
325
|
+
const nseTarget = pbxproj.hash.project.objects.PBXNativeTarget[target.uuid]
|
|
326
|
+
const configListUuid = nseTarget.buildConfigurationList
|
|
327
|
+
const configList = pbxproj.hash.project.objects.XCConfigurationList[configListUuid]
|
|
328
|
+
for (const { value: configUuid } of configList.buildConfigurations) {
|
|
329
|
+
const config = pbxproj.hash.project.objects.XCBuildConfiguration[configUuid]
|
|
330
|
+
Object.assign(config.buildSettings, settings)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @param {import('@expo/config-plugins').ExpoConfig} config
|
|
338
|
+
*/
|
|
339
|
+
const withSentoriNSETarget = (config) =>
|
|
340
|
+
withXcodeProject(config, (cfg) => {
|
|
341
|
+
const mainBundleId = cfg.ios?.bundleIdentifier
|
|
342
|
+
if (!mainBundleId) {
|
|
343
|
+
// Skip silently rather than throw — a host without an iOS
|
|
344
|
+
// bundleIdentifier configured is mid-setup, not broken.
|
|
345
|
+
// eslint-disable-next-line no-console
|
|
346
|
+
console.warn(
|
|
347
|
+
'[sentori-expo] ios.bundleIdentifier not set; skipping NSE target injection. Add it to app.json and re-prebuild.'
|
|
348
|
+
)
|
|
349
|
+
return cfg
|
|
350
|
+
}
|
|
351
|
+
// Match the host app's deployment target so the NSE links against
|
|
352
|
+
// the same minimum iOS. Multi-source fallback because cfg.ios?.deploymentTarget
|
|
353
|
+
// is not reliably populated — Expo readers prefer the
|
|
354
|
+
// `expo-build-properties` config plugin's value or the Podfile,
|
|
355
|
+
// not the top-level ios field. 15.1 matches Expo SDK 55's default.
|
|
356
|
+
const deploymentTarget =
|
|
357
|
+
cfg.ios?.deploymentTarget ?? cfg.ios?.infoPlist?.MinimumOSVersion ?? '15.1'
|
|
358
|
+
injectNSETarget(cfg.modResults, { deploymentTarget, mainBundleId })
|
|
359
|
+
return cfg
|
|
360
|
+
})
|
|
361
|
+
|
|
237
362
|
// ── Composer ───────────────────────────────────────────────────────
|
|
238
363
|
|
|
239
364
|
/**
|
|
@@ -244,7 +369,9 @@ const withSentori = (config, props = {}) => {
|
|
|
244
369
|
const plugins = [[withSentoriVersion, props]]
|
|
245
370
|
if (props.ios !== false) {
|
|
246
371
|
plugins.push([withSentoriPushIos, props])
|
|
247
|
-
if (props.nse !== false)
|
|
372
|
+
if (props.nse !== false) {
|
|
373
|
+
plugins.push([withSentoriNSE, props], [withSentoriNSETarget, props])
|
|
374
|
+
}
|
|
248
375
|
}
|
|
249
376
|
if (props.android !== false) {
|
|
250
377
|
plugins.push(
|
|
@@ -257,3 +384,7 @@ const withSentori = (config, props = {}) => {
|
|
|
257
384
|
}
|
|
258
385
|
|
|
259
386
|
module.exports = withSentori
|
|
387
|
+
// Exported for unit-test coverage of the pure helpers.
|
|
388
|
+
module.exports.injectNSETarget = injectNSETarget
|
|
389
|
+
module.exports.syncNSEPlistVersion = syncNSEPlistVersion
|
|
390
|
+
module.exports.NSE_TARGET = NSE_TARGET
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-expo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Expo adapter for Sentori — Config Plugin marker, expo-application auto-config, EAS post-build helper. Built on @goliapkg/sentori-react-native.",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"author": "GOLIA K.K. <takagi@golia.jp> (https://golia.jp)",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@goliapkg/sentori-react-native": ">=3.1.0",
|
|
50
|
-
"expo": ">=
|
|
51
|
-
"expo-application": ">=
|
|
52
|
-
"react-native": ">=0.
|
|
50
|
+
"expo": ">=56.0.0 <57.0.0",
|
|
51
|
+
"expo-application": ">=56.0.0 <57.0.0",
|
|
52
|
+
"react-native": ">=0.82.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"expo-application": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@expo/config-plugins": "
|
|
60
|
+
"@expo/config-plugins": "^56.0.9"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@goliapkg/sentori-react-native": "workspace:*",
|