expo-beacon 0.7.16 → 0.7.18

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,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-beacon",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "description": "Expo module for scanning, pairing, and monitoring iBeacons on Android and iOS",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1,5 +1,5 @@
1
1
  import { ConfigPlugin } from '@expo/config-plugins';
2
- export declare const IOS_PLUGIN_SWIFT = "import ExpoBeacon\nimport TSLocationManager\n\nfinal class BeaconGeoPlugin: BeaconLifecycleDelegate {\n func beaconDidEnter(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {\n TSLocationManager.sharedInstance().start()\n }\n func beaconDidExit(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {\n TSLocationManager.sharedInstance().stop()\n }\n func eddystoneDidEnter(identifier: String, namespace: String, instance: String, distance: Double) {\n TSLocationManager.sharedInstance().start()\n }\n func eddystoneDidExit(identifier: String, namespace: String, instance: String, distance: Double) {\n TSLocationManager.sharedInstance().stop()\n }\n}\n";
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,EACL,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAM9B,eAAO,MAAM,gBAAgB,uvBAkB5B,CAAC;AA2RF,QAAA,MAAM,aAAa,EAAE,YAkCpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
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,YAmEpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -1,257 +1,149 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.IOS_PLUGIN_SWIFT = void 0;
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
- exports.IOS_PLUGIN_SWIFT = `\
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.sharedInstance().start()
15
+ TSLocationManager.sharedManager().start()
15
16
  }
16
17
  func beaconDidExit(identifier: String, uuid: String, major: Int, minor: Int, distance: Double) {
17
- TSLocationManager.sharedInstance().stop()
18
+ TSLocationManager.sharedManager().stop()
18
19
  }
19
20
  func eddystoneDidEnter(identifier: String, namespace: String, instance: String, distance: Double) {
20
- TSLocationManager.sharedInstance().start()
21
+ TSLocationManager.sharedManager().start()
21
22
  }
22
23
  func eddystoneDidExit(identifier: String, namespace: String, instance: String, distance: Double) {
23
- TSLocationManager.sharedInstance().stop()
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
- function findPBXGroupKeyByName(xcodeProject, name) {
37
- var _a;
38
- const pbxGroups = (_a = xcodeProject.hash.project.objects['PBXGroup']) !== null && _a !== void 0 ? _a : {};
39
- // Prefer a group whose `path` equals the name that group IS the filesystem
40
- // directory. A group matched only by `name` may be a virtual (path-less)
41
- // container whose children resolve relative to its parent, not itself.
42
- let nameMatch = null;
43
- for (const key of Object.keys(pbxGroups)) {
44
- if (key.endsWith('_comment'))
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
- return nameMatch;
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 matches;
73
- }
74
- function removeFileReferenceFromAllGroups(xcodeProject, fileRefKeys) {
75
- var _a;
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
- function findPBXBuildFileKeys(xcodeProject, fileRefKeys, filename) {
90
- var _a;
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
- function removePBXSourcesBuildPhaseEntries(xcodeProject, buildFileKeys, filename) {
114
- var _a;
115
- if (buildFileKeys.length === 0)
116
- return;
117
- const buildFileKeySet = new Set(buildFileKeys);
118
- const sourcePhases = (_a = xcodeProject.hash.project.objects['PBXSourcesBuildPhase']) !== null && _a !== void 0 ? _a : {};
119
- for (const key of Object.keys(sourcePhases)) {
120
- if (key.endsWith('_comment'))
121
- continue;
122
- const phase = sourcePhases[key];
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
- function removePBXFileReferences(xcodeProject, fileReferences) {
133
- var _a;
134
- if (fileReferences.length === 0)
135
- return;
136
- const refs = (_a = xcodeProject.hash.project.objects['PBXFileReference']) !== null && _a !== void 0 ? _a : {};
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
- return undefined;
157
- }
158
- function ensureIOSPluginSourceFile(xcodeProject, projectName) {
159
- var _a, _b, _c;
160
- const pbxGroups = (_a = xcodeProject.hash.project.objects['PBXGroup']) !== null && _a !== void 0 ? _a : {};
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
- return;
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
- function modifySwiftAppDelegate(contents) {
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
- // Insert `BeaconLifecycleRegistry.register(BeaconGeoPlugin())` immediately
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 and add it to the Xcode project.
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 { projectName, platformProjectRoot } = config.modRequest;
237
- if (!projectName || !platformProjectRoot)
238
- return config;
239
- const swiftFilePath = path.join(platformProjectRoot, projectName, IOS_PLUGIN_FILENAME);
240
- fs.writeFileSync(swiftFilePath, exports.IOS_PLUGIN_SWIFT);
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
+ // pbxGroupByName returns the group object; addSourceFile needs the UUID key.
116
+ const groups = xcodeProject.hash.project.objects['PBXGroup'];
117
+ let groupKey;
118
+ for (const key of Object.keys(groups)) {
119
+ if (key.endsWith('_comment'))
120
+ continue;
121
+ const g = groups[key];
122
+ if (g.name === projectName || g.path === projectName) {
123
+ groupKey = key;
124
+ break;
125
+ }
126
+ }
127
+ xcodeProject.addSourceFile(filePath, { target: xcodeProject.getFirstTarget().uuid }, groupKey);
252
128
  }
253
129
  return config;
254
130
  });
131
+ // Step 3 – patch AppDelegate.swift to register the plugin.
132
+ config = (0, config_plugins_1.withDangerousMod)(config, [
133
+ 'ios',
134
+ (config) => {
135
+ const platformRoot = config.modRequest.platformProjectRoot;
136
+ const result = findAppDir(platformRoot);
137
+ if (!result) {
138
+ console.warn('[expo-beacon] AppDelegate.swift not found — ' +
139
+ 'please add BeaconLifecycleRegistry.register(BeaconGeoPlugin()) manually.');
140
+ return config;
141
+ }
142
+ const original = fs.readFileSync(result.appDelegatePath, 'utf-8');
143
+ fs.writeFileSync(result.appDelegatePath, modifyAppDelegateSwift(original));
144
+ return config;
145
+ },
146
+ ]);
255
147
  return config;
256
148
  };
257
149
  exports.default = withBeaconIOS;