expo-beacon 0.7.15 → 0.7.17
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/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConfigPlugin } from '@expo/config-plugins';
|
|
2
|
-
export declare
|
|
2
|
+
export declare function getIOSPluginSwift(): string;
|
|
3
3
|
declare const withBeaconIOS: ConfigPlugin;
|
|
4
4
|
export default withBeaconIOS;
|
|
5
5
|
//# sourceMappingURL=withBeaconIOS.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withBeaconIOS.d.ts","sourceRoot":"","sources":["../src/withBeaconIOS.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"withBeaconIOS.d.ts","sourceRoot":"","sources":["../src/withBeaconIOS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAsC,MAAM,sBAAsB,CAAC;AAMxF,wBAAgB,iBAAiB,IAAI,MAAM,CAoB1C;AA+ED,QAAA,MAAM,aAAa,EAAE,YAqDpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -1,257 +1,138 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getIOSPluginSwift = getIOSPluginSwift;
|
|
4
4
|
const config_plugins_1 = require("@expo/config-plugins");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
// ─── Generated Swift file ─────────────────────────────────────────────────────
|
|
8
|
-
|
|
8
|
+
function getIOSPluginSwift() {
|
|
9
|
+
return `\
|
|
9
10
|
import ExpoBeacon
|
|
10
11
|
import TSLocationManager
|
|
11
12
|
|
|
12
13
|
final class BeaconGeoPlugin: BeaconLifecycleDelegate {
|
|
13
14
|
func beaconDidEnter(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {
|
|
14
|
-
TSLocationManager.
|
|
15
|
+
TSLocationManager.sharedManager().start()
|
|
15
16
|
}
|
|
16
17
|
func beaconDidExit(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {
|
|
17
|
-
TSLocationManager.
|
|
18
|
+
TSLocationManager.sharedManager().stop()
|
|
18
19
|
}
|
|
19
20
|
func eddystoneDidEnter(identifier: String, namespace: String, instance: String, distance: Double) {
|
|
20
|
-
TSLocationManager.
|
|
21
|
+
TSLocationManager.sharedManager().start()
|
|
21
22
|
}
|
|
22
23
|
func eddystoneDidExit(identifier: String, namespace: String, instance: String, distance: Double) {
|
|
23
|
-
TSLocationManager.
|
|
24
|
+
TSLocationManager.sharedManager().stop()
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
`;
|
|
27
|
-
const IOS_PLUGIN_FILENAME = 'BeaconGeoPlugin.swift';
|
|
28
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
29
|
-
/**
|
|
30
|
-
* Finds the UUID (key) of a PBXGroup by name, handling both quoted and unquoted
|
|
31
|
-
* name values produced by node-xcode's parser.
|
|
32
|
-
*/
|
|
33
|
-
function normalizePBXValue(value) {
|
|
34
|
-
return typeof value === 'string' ? value.replace(/^"|"$/g, '') : '';
|
|
35
28
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
continue;
|
|
46
|
-
const group = pbxGroups[key];
|
|
47
|
-
if (normalizePBXValue(group.path) === name)
|
|
48
|
-
return key;
|
|
49
|
-
if (normalizePBXValue(group.name) === name && nameMatch === null) {
|
|
50
|
-
nameMatch = key;
|
|
29
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
30
|
+
function modifyAppDelegateSwift(contents) {
|
|
31
|
+
const importLine = 'import ExpoBeacon';
|
|
32
|
+
// 1. Add import after the last existing import line.
|
|
33
|
+
if (!contents.includes(importLine)) {
|
|
34
|
+
const lines = contents.split('\n');
|
|
35
|
+
const lastImportIdx = lines.reduce((last, line, i) => (line.trimStart().startsWith('import ') ? i : last), -1);
|
|
36
|
+
if (lastImportIdx >= 0) {
|
|
37
|
+
lines.splice(lastImportIdx + 1, 0, importLine);
|
|
51
38
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
function findPBXFileReferences(xcodeProject, filename) {
|
|
56
|
-
var _a;
|
|
57
|
-
const refs = (_a = xcodeProject.hash.project.objects['PBXFileReference']) !== null && _a !== void 0 ? _a : {};
|
|
58
|
-
const matches = [];
|
|
59
|
-
for (const key of Object.keys(refs)) {
|
|
60
|
-
if (key.endsWith('_comment'))
|
|
61
|
-
continue;
|
|
62
|
-
const ref = refs[key];
|
|
63
|
-
if (typeof ref !== 'object' || ref === null)
|
|
64
|
-
continue;
|
|
65
|
-
const refPath = normalizePBXValue(ref.path);
|
|
66
|
-
const refName = normalizePBXValue(ref.name);
|
|
67
|
-
if (path.posix.basename(refPath) === filename ||
|
|
68
|
-
path.posix.basename(refName) === filename) {
|
|
69
|
-
matches.push({ key, ref });
|
|
39
|
+
else {
|
|
40
|
+
lines.splice(0, 0, importLine, '');
|
|
70
41
|
}
|
|
42
|
+
contents = lines.join('\n');
|
|
71
43
|
}
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (fileRefKeys.length === 0)
|
|
77
|
-
return;
|
|
78
|
-
const fileRefKeySet = new Set(fileRefKeys);
|
|
79
|
-
const pbxGroups = (_a = xcodeProject.hash.project.objects['PBXGroup']) !== null && _a !== void 0 ? _a : {};
|
|
80
|
-
for (const key of Object.keys(pbxGroups)) {
|
|
81
|
-
if (key.endsWith('_comment'))
|
|
82
|
-
continue;
|
|
83
|
-
const group = pbxGroups[key];
|
|
84
|
-
if (!Array.isArray(group.children))
|
|
85
|
-
continue;
|
|
86
|
-
group.children = group.children.filter((child) => { var _a; return !fileRefKeySet.has((_a = child.value) !== null && _a !== void 0 ? _a : ''); });
|
|
44
|
+
// 2. Insert registration call before `return super.application(…didFinishLaunchingWithOptions…)`.
|
|
45
|
+
const registrationCall = 'BeaconLifecycleRegistry.register(BeaconGeoPlugin())';
|
|
46
|
+
if (contents.includes(registrationCall)) {
|
|
47
|
+
return contents; // already patched
|
|
87
48
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const fileRefKeySet = new Set(fileRefKeys);
|
|
92
|
-
const buildFiles = (_a = xcodeProject.hash.project.objects['PBXBuildFile']) !== null && _a !== void 0 ? _a : {};
|
|
93
|
-
return Object.keys(buildFiles).filter((key) => {
|
|
94
|
-
if (key.endsWith('_comment'))
|
|
95
|
-
return false;
|
|
96
|
-
const buildFile = buildFiles[key];
|
|
97
|
-
if (typeof buildFile !== 'object' || buildFile === null)
|
|
98
|
-
return false;
|
|
99
|
-
return (fileRefKeySet.has(buildFile.fileRef) ||
|
|
100
|
-
normalizePBXValue(buildFile.fileRef_comment) === filename);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
function removePBXBuildFiles(xcodeProject, buildFileKeys) {
|
|
104
|
-
var _a;
|
|
105
|
-
if (buildFileKeys.length === 0)
|
|
106
|
-
return;
|
|
107
|
-
const buildFiles = (_a = xcodeProject.hash.project.objects['PBXBuildFile']) !== null && _a !== void 0 ? _a : {};
|
|
108
|
-
for (const key of buildFileKeys) {
|
|
109
|
-
delete buildFiles[key];
|
|
110
|
-
delete buildFiles[`${key}_comment`];
|
|
49
|
+
if (/return super\.application\(application,\s*didFinishLaunching/.test(contents)) {
|
|
50
|
+
contents = contents.replace(/([ \t]*)(return super\.application\(application,\s*didFinishLaunchingWithOptions[^)]*\))/, `$1${registrationCall}\n$1$2`);
|
|
51
|
+
return contents;
|
|
111
52
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (!Array.isArray(phase.files))
|
|
124
|
-
continue;
|
|
125
|
-
phase.files = phase.files.filter((file) => {
|
|
126
|
-
var _a;
|
|
127
|
-
return !buildFileKeySet.has((_a = file.value) !== null && _a !== void 0 ? _a : '') &&
|
|
128
|
-
normalizePBXValue(file.comment) !== `${filename} in Sources`;
|
|
129
|
-
});
|
|
53
|
+
// Fallback: no `didFinishLaunchingWithOptions` override found — inject one before
|
|
54
|
+
// the last closing brace of the class so it is always picked up by the compiler.
|
|
55
|
+
const methodOverride = `
|
|
56
|
+
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
57
|
+
${registrationCall}
|
|
58
|
+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
const lastBraceIdx = contents.lastIndexOf('}');
|
|
62
|
+
if (lastBraceIdx >= 0) {
|
|
63
|
+
contents = contents.slice(0, lastBraceIdx) + methodOverride + contents.slice(lastBraceIdx);
|
|
130
64
|
}
|
|
65
|
+
return contents;
|
|
131
66
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
for (const { key } of fileReferences) {
|
|
138
|
-
delete refs[key];
|
|
139
|
-
delete refs[`${key}_comment`];
|
|
67
|
+
/** Return the first subdirectory of `platformRoot` that contains AppDelegate.swift. */
|
|
68
|
+
function findAppDir(platformRoot) {
|
|
69
|
+
let entries;
|
|
70
|
+
try {
|
|
71
|
+
entries = fs.readdirSync(platformRoot);
|
|
140
72
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* Returns the key of the project's mainGroup — the root PBXGroup that is
|
|
144
|
-
* always present in every Xcode project.
|
|
145
|
-
*/
|
|
146
|
-
function getMainGroupKey(xcodeProject) {
|
|
147
|
-
var _a;
|
|
148
|
-
const pbxProjects = (_a = xcodeProject.hash.project.objects['PBXProject']) !== null && _a !== void 0 ? _a : {};
|
|
149
|
-
for (const key of Object.keys(pbxProjects)) {
|
|
150
|
-
if (key.endsWith('_comment'))
|
|
151
|
-
continue;
|
|
152
|
-
const proj = pbxProjects[key];
|
|
153
|
-
if (proj === null || proj === void 0 ? void 0 : proj.mainGroup)
|
|
154
|
-
return proj.mainGroup;
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
155
75
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const groupKey = (_b = findPBXGroupKeyByName(xcodeProject, projectName)) !== null && _b !== void 0 ? _b : undefined;
|
|
162
|
-
// A physical group has `path` equal to the project name; its children resolve
|
|
163
|
-
// relative to ios/<AppName>/. A virtual (name-only) group has no `path`, so
|
|
164
|
-
// its children resolve relative to its parent — typically SOURCE_ROOT (ios/).
|
|
165
|
-
const groupHasPhysicalPath = groupKey != null &&
|
|
166
|
-
normalizePBXValue((_c = pbxGroups[groupKey]) === null || _c === void 0 ? void 0 : _c.path) === projectName;
|
|
167
|
-
// The PBXFileReference `path` that makes Xcode resolve ios/<AppName>/file:
|
|
168
|
-
// • physical group → just the basename (the group chain supplies the dir)
|
|
169
|
-
// • virtual / no group → SOURCE_ROOT-relative path
|
|
170
|
-
const fileRefPath = groupHasPhysicalPath
|
|
171
|
-
? IOS_PLUGIN_FILENAME
|
|
172
|
-
: `${projectName}/${IOS_PLUGIN_FILENAME}`;
|
|
173
|
-
// IMPORTANT: xcodeProject.addSourceFile() MUST always receive a group key.
|
|
174
|
-
// Without one it calls addPluginFile() which looks up a "Plugins" PBXGroup
|
|
175
|
-
// that may not exist, dereferencing null and crashing prebuild.
|
|
176
|
-
// Fall back to the project's mainGroup, which is guaranteed to be present.
|
|
177
|
-
const effectiveGroupKey = groupKey !== null && groupKey !== void 0 ? groupKey : getMainGroupKey(xcodeProject);
|
|
178
|
-
const existingFileReferences = findPBXFileReferences(xcodeProject, IOS_PLUGIN_FILENAME);
|
|
179
|
-
const existingBuildFileKeys = findPBXBuildFileKeys(xcodeProject, existingFileReferences.map(({ key }) => key), IOS_PLUGIN_FILENAME);
|
|
180
|
-
if (existingFileReferences.length === 1 &&
|
|
181
|
-
existingBuildFileKeys.length === 1) {
|
|
182
|
-
// Repair the single existing entry in-place.
|
|
183
|
-
const [existingFileReference] = existingFileReferences;
|
|
184
|
-
existingFileReference.ref.name = `"${IOS_PLUGIN_FILENAME}"`;
|
|
185
|
-
existingFileReference.ref.sourceTree = '"<group>"';
|
|
186
|
-
existingFileReference.ref.path = `"${fileRefPath}"`;
|
|
187
|
-
if (effectiveGroupKey) {
|
|
188
|
-
removeFileReferenceFromAllGroups(xcodeProject, [existingFileReference.key]);
|
|
189
|
-
xcodeProject.addToPbxGroup({ fileRef: existingFileReference.key, basename: IOS_PLUGIN_FILENAME }, effectiveGroupKey);
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const dirPath = path.join(platformRoot, entry);
|
|
78
|
+
try {
|
|
79
|
+
if (!fs.statSync(dirPath).isDirectory())
|
|
80
|
+
continue;
|
|
190
81
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Remove any stale/duplicate entries before re-adding.
|
|
194
|
-
if (existingFileReferences.length > 0 || existingBuildFileKeys.length > 0) {
|
|
195
|
-
removePBXSourcesBuildPhaseEntries(xcodeProject, existingBuildFileKeys, IOS_PLUGIN_FILENAME);
|
|
196
|
-
removePBXBuildFiles(xcodeProject, existingBuildFileKeys);
|
|
197
|
-
removeFileReferenceFromAllGroups(xcodeProject, existingFileReferences.map(({ key }) => key));
|
|
198
|
-
removePBXFileReferences(xcodeProject, existingFileReferences);
|
|
199
|
-
}
|
|
200
|
-
// Always pass effectiveGroupKey so the xcode library never falls through to
|
|
201
|
-
// its addPluginFile / Plugins-group lookup path.
|
|
202
|
-
xcodeProject.addSourceFile(IOS_PLUGIN_FILENAME, null, effectiveGroupKey);
|
|
203
|
-
// For non-physical groups the file reference path defaults to the bare
|
|
204
|
-
// filename; patch it to the SOURCE_ROOT-relative path so Xcode finds the
|
|
205
|
-
// file at ios/<AppName>/BeaconGeoPlugin.swift.
|
|
206
|
-
if (!groupHasPhysicalPath) {
|
|
207
|
-
const newRefs = findPBXFileReferences(xcodeProject, IOS_PLUGIN_FILENAME);
|
|
208
|
-
for (const { ref } of newRefs) {
|
|
209
|
-
ref.path = `"${fileRefPath}"`;
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
210
84
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Insert `import ExpoBeacon` after the last existing import line.
|
|
215
|
-
if (!contents.includes('import ExpoBeacon')) {
|
|
216
|
-
const lines = contents.split('\n');
|
|
217
|
-
const lastImportIdx = lines.reduce((last, line, i) => (line.trimStart().startsWith('import ') ? i : last), -1);
|
|
218
|
-
if (lastImportIdx >= 0) {
|
|
219
|
-
lines.splice(lastImportIdx + 1, 0, 'import ExpoBeacon');
|
|
220
|
-
contents = lines.join('\n');
|
|
85
|
+
const swiftPath = path.join(dirPath, 'AppDelegate.swift');
|
|
86
|
+
if (fs.existsSync(swiftPath)) {
|
|
87
|
+
return { appDir: entry, appDelegatePath: swiftPath };
|
|
221
88
|
}
|
|
222
89
|
}
|
|
223
|
-
|
|
224
|
-
// before `return super.application(`, preserving indentation.
|
|
225
|
-
const marker = 'BeaconLifecycleRegistry.register(BeaconGeoPlugin())';
|
|
226
|
-
if (!contents.includes(marker)) {
|
|
227
|
-
contents = contents.replace(/([ \t]*)(return super\.application\()/, `$1${marker}\n$1$2`);
|
|
228
|
-
}
|
|
229
|
-
return contents;
|
|
90
|
+
return null;
|
|
230
91
|
}
|
|
231
92
|
// ─── Plugin ───────────────────────────────────────────────────────────────────
|
|
232
93
|
const withBeaconIOS = (config) => {
|
|
233
|
-
// Step 1 – write BeaconGeoPlugin.swift
|
|
94
|
+
// Step 1 – write BeaconGeoPlugin.swift into the iOS app directory.
|
|
95
|
+
config = (0, config_plugins_1.withDangerousMod)(config, [
|
|
96
|
+
'ios',
|
|
97
|
+
(config) => {
|
|
98
|
+
const platformRoot = config.modRequest.platformProjectRoot;
|
|
99
|
+
const result = findAppDir(platformRoot);
|
|
100
|
+
if (!result) {
|
|
101
|
+
console.warn('[expo-beacon] Could not locate iOS app directory — BeaconGeoPlugin.swift was not written.');
|
|
102
|
+
return config;
|
|
103
|
+
}
|
|
104
|
+
const outputPath = path.join(platformRoot, result.appDir, 'BeaconGeoPlugin.swift');
|
|
105
|
+
fs.writeFileSync(outputPath, getIOSPluginSwift());
|
|
106
|
+
return config;
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
// Step 2 – add BeaconGeoPlugin.swift to the Xcode project target.
|
|
234
110
|
config = (0, config_plugins_1.withXcodeProject)(config, (config) => {
|
|
235
111
|
const xcodeProject = config.modResults;
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
ensureIOSPluginSourceFile(xcodeProject, projectName);
|
|
242
|
-
return config;
|
|
243
|
-
});
|
|
244
|
-
// Step 2 – patch AppDelegate.swift to register the plugin before super.
|
|
245
|
-
config = (0, config_plugins_1.withAppDelegate)(config, (config) => {
|
|
246
|
-
if (config.modResults.language === 'swift') {
|
|
247
|
-
config.modResults.contents = modifySwiftAppDelegate(config.modResults.contents);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
console.warn('[expo-beacon] withBeaconIOS: AppDelegate is not Swift — ' +
|
|
251
|
-
'please add BeaconLifecycleRegistry.register(BeaconGeoPlugin()) manually.');
|
|
112
|
+
const projectName = config.modRequest.projectName;
|
|
113
|
+
const filePath = `${projectName}/BeaconGeoPlugin.swift`;
|
|
114
|
+
if (!xcodeProject.hasFile(filePath)) {
|
|
115
|
+
const group = xcodeProject.pbxGroupByName(projectName);
|
|
116
|
+
xcodeProject.addSourceFile(filePath, {}, group);
|
|
252
117
|
}
|
|
253
118
|
return config;
|
|
254
119
|
});
|
|
120
|
+
// Step 3 – patch AppDelegate.swift to register the plugin.
|
|
121
|
+
config = (0, config_plugins_1.withDangerousMod)(config, [
|
|
122
|
+
'ios',
|
|
123
|
+
(config) => {
|
|
124
|
+
const platformRoot = config.modRequest.platformProjectRoot;
|
|
125
|
+
const result = findAppDir(platformRoot);
|
|
126
|
+
if (!result) {
|
|
127
|
+
console.warn('[expo-beacon] AppDelegate.swift not found — ' +
|
|
128
|
+
'please add BeaconLifecycleRegistry.register(BeaconGeoPlugin()) manually.');
|
|
129
|
+
return config;
|
|
130
|
+
}
|
|
131
|
+
const original = fs.readFileSync(result.appDelegatePath, 'utf-8');
|
|
132
|
+
fs.writeFileSync(result.appDelegatePath, modifyAppDelegateSwift(original));
|
|
133
|
+
return config;
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
255
136
|
return config;
|
|
256
137
|
};
|
|
257
138
|
exports.default = withBeaconIOS;
|