expo-beacon 0.7.10 → 0.7.12
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.
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
2
|
xmlns:tools="http://schemas.android.com/tools">
|
|
3
3
|
|
|
4
|
-
<!-- Bluetooth permissions
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
<!-- Bluetooth permissions.
|
|
5
|
+
BLUETOOTH and BLUETOOTH_ADMIN are legacy normal permissions required by AltBeacon's
|
|
6
|
+
PermissionsInspector on all API levels. On Android 12+ they are automatically granted
|
|
7
|
+
and act as no-ops, but removing them (via maxSdkVersion="30") causes AltBeacon to log
|
|
8
|
+
errors and believe BLE scanning is unavailable.
|
|
9
|
+
BLUETOOTH_SCAN and BLUETOOTH_CONNECT cover the runtime permission requirements on API 31+. -->
|
|
10
|
+
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
11
|
+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
12
|
+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
13
|
+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
7
14
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
8
15
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
9
16
|
|
|
@@ -61,6 +61,12 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
61
61
|
private val notifIdCounter = AtomicInteger(0)
|
|
62
62
|
private val notifIdMap = java.util.concurrent.ConcurrentHashMap<String, Int>()
|
|
63
63
|
|
|
64
|
+
// Debounce guard: prevent loadAndMonitorRegions() from running more than once per 500 ms.
|
|
65
|
+
// This protects against rapid double-invocations when JS calls stopMonitoring/startMonitoring
|
|
66
|
+
// in quick succession during activity lifecycle transitions (e.g. onHostPause → onHostResume),
|
|
67
|
+
// which would otherwise stop and restart all AltBeacon regions within milliseconds.
|
|
68
|
+
@Volatile private var lastLoadRegionsMs: Long = 0L
|
|
69
|
+
|
|
64
70
|
// Distance logging
|
|
65
71
|
private val distanceLogRegions = java.util.concurrent.CopyOnWriteArraySet<Region>()
|
|
66
72
|
|
|
@@ -189,6 +195,14 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
private fun loadAndMonitorRegions() {
|
|
198
|
+
val now = android.os.SystemClock.elapsedRealtime()
|
|
199
|
+
val elapsed = now - lastLoadRegionsMs
|
|
200
|
+
if (elapsed < LOAD_REGIONS_DEBOUNCE_MS) {
|
|
201
|
+
Log.d(TAG, "loadAndMonitorRegions: skipping duplicate call (${elapsed}ms after last load)")
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
lastLoadRegionsMs = now
|
|
205
|
+
|
|
192
206
|
val prefs: SharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
193
207
|
val json = prefs.getString(PREFS_KEY, "[]") ?: "[]"
|
|
194
208
|
val beacons = try { JSONArray(json) } catch (_: Exception) { JSONArray() }
|
|
@@ -799,6 +813,8 @@ class BeaconForegroundService : Service(), BeaconConsumer {
|
|
|
799
813
|
private const val MAX_STARTFOREGROUND_RETRIES = 3
|
|
800
814
|
private const val RETRY_DELAY_MS = 10_000L
|
|
801
815
|
private const val RETRY_SERVICE_REQUEST_CODE = 0x42454143 // "BEAC"
|
|
816
|
+
/** Minimum milliseconds between consecutive loadAndMonitorRegions() calls. */
|
|
817
|
+
private const val LOAD_REGIONS_DEBOUNCE_MS = 500L
|
|
802
818
|
@Volatile private var activeService: BeaconForegroundService? = null
|
|
803
819
|
|
|
804
820
|
fun start(context: Context) {
|
package/package.json
CHANGED
|
@@ -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,mvBAkB5B,CAAC;
|
|
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,mvBAkB5B,CAAC;AA8OF,QAAA,MAAM,aAAa,EAAE,YAkCpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -24,11 +24,15 @@ final class BeaconGeoPlugin: BeaconLifecycleDelegate {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
`;
|
|
27
|
+
const IOS_PLUGIN_FILENAME = 'BeaconGeoPlugin.swift';
|
|
27
28
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
28
29
|
/**
|
|
29
30
|
* Finds the UUID (key) of a PBXGroup by name, handling both quoted and unquoted
|
|
30
31
|
* name values produced by node-xcode's parser.
|
|
31
32
|
*/
|
|
33
|
+
function normalizePBXValue(value) {
|
|
34
|
+
return typeof value === 'string' ? value.replace(/^"|"$/g, '') : '';
|
|
35
|
+
}
|
|
32
36
|
function findPBXGroupKeyByName(xcodeProject, name) {
|
|
33
37
|
var _a;
|
|
34
38
|
const pbxGroups = (_a = xcodeProject.hash.project.objects['PBXGroup']) !== null && _a !== void 0 ? _a : {};
|
|
@@ -36,18 +40,133 @@ function findPBXGroupKeyByName(xcodeProject, name) {
|
|
|
36
40
|
if (key.endsWith('_comment'))
|
|
37
41
|
continue;
|
|
38
42
|
const group = pbxGroups[key];
|
|
39
|
-
if (group.name ===
|
|
43
|
+
if (normalizePBXValue(group.name) === name ||
|
|
44
|
+
normalizePBXValue(group.path) === name) {
|
|
40
45
|
return key;
|
|
46
|
+
}
|
|
41
47
|
}
|
|
42
48
|
return null;
|
|
43
49
|
}
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
function findPBXFileReferences(xcodeProject, filename) {
|
|
51
|
+
var _a;
|
|
52
|
+
const refs = (_a = xcodeProject.hash.project.objects['PBXFileReference']) !== null && _a !== void 0 ? _a : {};
|
|
53
|
+
const matches = [];
|
|
54
|
+
for (const key of Object.keys(refs)) {
|
|
55
|
+
if (key.endsWith('_comment'))
|
|
56
|
+
continue;
|
|
57
|
+
const ref = refs[key];
|
|
58
|
+
if (typeof ref !== 'object' || ref === null)
|
|
59
|
+
continue;
|
|
60
|
+
const refPath = normalizePBXValue(ref.path);
|
|
61
|
+
const refName = normalizePBXValue(ref.name);
|
|
62
|
+
if (path.posix.basename(refPath) === filename ||
|
|
63
|
+
path.posix.basename(refName) === filename) {
|
|
64
|
+
matches.push({ key, ref });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return matches;
|
|
68
|
+
}
|
|
69
|
+
function removeFileReferenceFromAllGroups(xcodeProject, fileRefKeys) {
|
|
70
|
+
var _a;
|
|
71
|
+
if (fileRefKeys.length === 0)
|
|
72
|
+
return;
|
|
73
|
+
const fileRefKeySet = new Set(fileRefKeys);
|
|
74
|
+
const pbxGroups = (_a = xcodeProject.hash.project.objects['PBXGroup']) !== null && _a !== void 0 ? _a : {};
|
|
75
|
+
for (const key of Object.keys(pbxGroups)) {
|
|
76
|
+
if (key.endsWith('_comment'))
|
|
77
|
+
continue;
|
|
78
|
+
const group = pbxGroups[key];
|
|
79
|
+
if (!Array.isArray(group.children))
|
|
80
|
+
continue;
|
|
81
|
+
group.children = group.children.filter((child) => { var _a; return !fileRefKeySet.has((_a = child.value) !== null && _a !== void 0 ? _a : ''); });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function findPBXBuildFileKeys(xcodeProject, fileRefKeys, filename) {
|
|
85
|
+
var _a;
|
|
86
|
+
const fileRefKeySet = new Set(fileRefKeys);
|
|
87
|
+
const buildFiles = (_a = xcodeProject.hash.project.objects['PBXBuildFile']) !== null && _a !== void 0 ? _a : {};
|
|
88
|
+
return Object.keys(buildFiles).filter((key) => {
|
|
89
|
+
if (key.endsWith('_comment'))
|
|
90
|
+
return false;
|
|
91
|
+
const buildFile = buildFiles[key];
|
|
92
|
+
if (typeof buildFile !== 'object' || buildFile === null)
|
|
93
|
+
return false;
|
|
94
|
+
return (fileRefKeySet.has(buildFile.fileRef) ||
|
|
95
|
+
normalizePBXValue(buildFile.fileRef_comment) === filename);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function removePBXBuildFiles(xcodeProject, buildFileKeys) {
|
|
99
|
+
var _a;
|
|
100
|
+
if (buildFileKeys.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
const buildFiles = (_a = xcodeProject.hash.project.objects['PBXBuildFile']) !== null && _a !== void 0 ? _a : {};
|
|
103
|
+
for (const key of buildFileKeys) {
|
|
104
|
+
delete buildFiles[key];
|
|
105
|
+
delete buildFiles[`${key}_comment`];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function removePBXSourcesBuildPhaseEntries(xcodeProject, buildFileKeys, filename) {
|
|
109
|
+
var _a;
|
|
110
|
+
if (buildFileKeys.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const buildFileKeySet = new Set(buildFileKeys);
|
|
113
|
+
const sourcePhases = (_a = xcodeProject.hash.project.objects['PBXSourcesBuildPhase']) !== null && _a !== void 0 ? _a : {};
|
|
114
|
+
for (const key of Object.keys(sourcePhases)) {
|
|
115
|
+
if (key.endsWith('_comment'))
|
|
116
|
+
continue;
|
|
117
|
+
const phase = sourcePhases[key];
|
|
118
|
+
if (!Array.isArray(phase.files))
|
|
119
|
+
continue;
|
|
120
|
+
phase.files = phase.files.filter((file) => {
|
|
121
|
+
var _a;
|
|
122
|
+
return !buildFileKeySet.has((_a = file.value) !== null && _a !== void 0 ? _a : '') &&
|
|
123
|
+
normalizePBXValue(file.comment) !== `${filename} in Sources`;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function removePBXFileReferences(xcodeProject, fileReferences) {
|
|
46
128
|
var _a;
|
|
129
|
+
if (fileReferences.length === 0)
|
|
130
|
+
return;
|
|
47
131
|
const refs = (_a = xcodeProject.hash.project.objects['PBXFileReference']) !== null && _a !== void 0 ? _a : {};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
132
|
+
for (const { key } of fileReferences) {
|
|
133
|
+
delete refs[key];
|
|
134
|
+
delete refs[`${key}_comment`];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function ensureIOSPluginSourceFile(xcodeProject, projectName) {
|
|
138
|
+
var _a;
|
|
139
|
+
const groupKey = (_a = findPBXGroupKeyByName(xcodeProject, projectName)) !== null && _a !== void 0 ? _a : undefined;
|
|
140
|
+
const existingFileReferences = findPBXFileReferences(xcodeProject, IOS_PLUGIN_FILENAME);
|
|
141
|
+
const existingBuildFileKeys = findPBXBuildFileKeys(xcodeProject, existingFileReferences.map(({ key }) => key), IOS_PLUGIN_FILENAME);
|
|
142
|
+
if (existingFileReferences.length === 1 &&
|
|
143
|
+
existingBuildFileKeys.length === 1) {
|
|
144
|
+
const [existingFileReference] = existingFileReferences;
|
|
145
|
+
existingFileReference.ref.name = `"${IOS_PLUGIN_FILENAME}"`;
|
|
146
|
+
existingFileReference.ref.sourceTree = '"<group>"';
|
|
147
|
+
if (groupKey) {
|
|
148
|
+
removeFileReferenceFromAllGroups(xcodeProject, [existingFileReference.key]);
|
|
149
|
+
xcodeProject.addToPbxGroup({
|
|
150
|
+
fileRef: existingFileReference.key,
|
|
151
|
+
basename: IOS_PLUGIN_FILENAME,
|
|
152
|
+
}, groupKey);
|
|
153
|
+
existingFileReference.ref.path = `"${IOS_PLUGIN_FILENAME}"`;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
existingFileReference.ref.path = `"${projectName}/${IOS_PLUGIN_FILENAME}"`;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (existingFileReferences.length > 0 || existingBuildFileKeys.length > 0) {
|
|
160
|
+
removePBXSourcesBuildPhaseEntries(xcodeProject, existingBuildFileKeys, IOS_PLUGIN_FILENAME);
|
|
161
|
+
removePBXBuildFiles(xcodeProject, existingBuildFileKeys);
|
|
162
|
+
removeFileReferenceFromAllGroups(xcodeProject, existingFileReferences.map(({ key }) => key));
|
|
163
|
+
removePBXFileReferences(xcodeProject, existingFileReferences);
|
|
164
|
+
}
|
|
165
|
+
if (groupKey) {
|
|
166
|
+
xcodeProject.addSourceFile(IOS_PLUGIN_FILENAME, null, groupKey);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
xcodeProject.addSourceFile(`${projectName}/${IOS_PLUGIN_FILENAME}`);
|
|
51
170
|
}
|
|
52
171
|
function modifySwiftAppDelegate(contents) {
|
|
53
172
|
// Insert `import ExpoBeacon` after the last existing import line.
|
|
@@ -71,17 +190,13 @@ function modifySwiftAppDelegate(contents) {
|
|
|
71
190
|
const withBeaconIOS = (config) => {
|
|
72
191
|
// Step 1 – write BeaconGeoPlugin.swift and add it to the Xcode project.
|
|
73
192
|
config = (0, config_plugins_1.withXcodeProject)(config, (config) => {
|
|
74
|
-
var _a;
|
|
75
193
|
const xcodeProject = config.modResults;
|
|
76
194
|
const { projectName, platformProjectRoot } = config.modRequest;
|
|
77
195
|
if (!projectName || !platformProjectRoot)
|
|
78
196
|
return config;
|
|
79
|
-
const swiftFilePath = path.join(platformProjectRoot, projectName,
|
|
197
|
+
const swiftFilePath = path.join(platformProjectRoot, projectName, IOS_PLUGIN_FILENAME);
|
|
80
198
|
fs.writeFileSync(swiftFilePath, exports.IOS_PLUGIN_SWIFT);
|
|
81
|
-
|
|
82
|
-
const groupKey = (_a = findPBXGroupKeyByName(xcodeProject, projectName)) !== null && _a !== void 0 ? _a : undefined;
|
|
83
|
-
xcodeProject.addSourceFile('BeaconGeoPlugin.swift', null, groupKey !== null && groupKey !== void 0 ? groupKey : undefined);
|
|
84
|
-
}
|
|
199
|
+
ensureIOSPluginSourceFile(xcodeProject, projectName);
|
|
85
200
|
return config;
|
|
86
201
|
});
|
|
87
202
|
// Step 2 – patch AppDelegate.swift to register the plugin before super.
|