@goliapkg/sentori-expo 7.0.0 → 7.0.2
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 +162 -27
- package/package.json +1 -1
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'
|
|
@@ -95,9 +104,13 @@ const withSentoriPushIos = (config) => {
|
|
|
95
104
|
*/
|
|
96
105
|
const withSentoriPushAndroidManifest = (config) => {
|
|
97
106
|
return withAndroidManifest(config, (cfg) => {
|
|
98
|
-
|
|
107
|
+
// addPermission expects the AndroidManifest object (cfg.modResults);
|
|
108
|
+
// it dereferences `.manifest['uses-permission']` internally. Passing
|
|
109
|
+
// `cfg.modResults.manifest` makes that read fail with `Cannot read
|
|
110
|
+
// properties of undefined (reading 'uses-permission')` and crashes
|
|
111
|
+
// `expo prebuild`.
|
|
99
112
|
AndroidConfig.Permissions.addPermission(
|
|
100
|
-
|
|
113
|
+
cfg.modResults,
|
|
101
114
|
'android.permission.POST_NOTIFICATIONS'
|
|
102
115
|
)
|
|
103
116
|
return cfg
|
|
@@ -168,7 +181,7 @@ const withSentoriGoogleServicesJson = (config, props = {}) => {
|
|
|
168
181
|
])
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
// ── v2.28 iOS Notification Service Extension
|
|
184
|
+
// ── v2.28 iOS Notification Service Extension ─────────────────────
|
|
172
185
|
//
|
|
173
186
|
// Rich-media notifications (images / future video) require an NSE
|
|
174
187
|
// target on the iOS app. The Sentori NSE template downloads the URL
|
|
@@ -176,14 +189,48 @@ const withSentoriGoogleServicesJson = (config, props = {}) => {
|
|
|
176
189
|
// displays the notification. APNs server side sets this key when
|
|
177
190
|
// `richMedia.imageUrl` is on the send (v2.28+).
|
|
178
191
|
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
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.
|
|
184
207
|
//
|
|
185
|
-
// Opt out with `{ ios: false }` (which
|
|
186
|
-
//
|
|
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
|
+
}
|
|
187
234
|
|
|
188
235
|
/**
|
|
189
236
|
* @param {import('@expo/config-plugins').ExpoConfig} config
|
|
@@ -193,7 +240,7 @@ const withSentoriNSE = (config) => {
|
|
|
193
240
|
'ios',
|
|
194
241
|
async (cfg) => {
|
|
195
242
|
const platformRoot = cfg.modRequest.platformProjectRoot
|
|
196
|
-
const destDir = path.join(platformRoot,
|
|
243
|
+
const destDir = path.join(platformRoot, NSE_TARGET)
|
|
197
244
|
const templateDir = path.join(__dirname, 'templates', 'ios-nse')
|
|
198
245
|
const swiftSrc = path.join(templateDir, 'SentoriNotificationServiceExtension.swift')
|
|
199
246
|
const plistSrc = path.join(templateDir, 'SentoriNSE-Info.plist')
|
|
@@ -209,27 +256,109 @@ const withSentoriNSE = (config) => {
|
|
|
209
256
|
swiftSrc,
|
|
210
257
|
path.join(destDir, 'SentoriNotificationServiceExtension.swift')
|
|
211
258
|
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
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)
|
|
227
273
|
}
|
|
228
274
|
return cfg
|
|
229
275
|
},
|
|
230
276
|
])
|
|
231
277
|
}
|
|
232
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
|
+
|
|
233
362
|
// ── Composer ───────────────────────────────────────────────────────
|
|
234
363
|
|
|
235
364
|
/**
|
|
@@ -240,7 +369,9 @@ const withSentori = (config, props = {}) => {
|
|
|
240
369
|
const plugins = [[withSentoriVersion, props]]
|
|
241
370
|
if (props.ios !== false) {
|
|
242
371
|
plugins.push([withSentoriPushIos, props])
|
|
243
|
-
if (props.nse !== false)
|
|
372
|
+
if (props.nse !== false) {
|
|
373
|
+
plugins.push([withSentoriNSE, props], [withSentoriNSETarget, props])
|
|
374
|
+
}
|
|
244
375
|
}
|
|
245
376
|
if (props.android !== false) {
|
|
246
377
|
plugins.push(
|
|
@@ -253,3 +384,7 @@ const withSentori = (config, props = {}) => {
|
|
|
253
384
|
}
|
|
254
385
|
|
|
255
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": "7.0.
|
|
3
|
+
"version": "7.0.2",
|
|
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)",
|