From f499351b212944cf5204d8fd739ec55107b27d0e Mon Sep 17 00:00:00 2001 From: aria Date: Wed, 22 Apr 2026 20:07:06 +0900 Subject: [PATCH] fixed ios wifi part support. retry 3. --- README.md | 2 + package.json | 2 +- plugin.xml | 2 +- src/ios/CDVBluememeUtils.m | 148 ++++++++++++++++++++++++++++--------- 4 files changed, 118 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 6a167f3..6a2a564 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ WiFi options: - `password`:开放网络可省略 - `security`:`open`、`wep`、`wpa2`、`wpa3`,默认按是否存在 password 推断为 `open` 或 `wpa2` - `hiddenSsid`:可选;Android 支持隐藏 SSID;iOS 13+ 会设置 `NEHotspotConfiguration.hidden` +- `resetExisting`:可选,仅 iOS,默认 `true`;应用 WiFi 配置前会移除本 App 以前写入过的同 SSID 配置,再重新应用 - `bssid`:可选,仅 Android 10+ 临时连接使用 - `joinOnce`:可选,仅 iOS;`connectWifi` 默认 `true`,`saveWifi` 默认 `false` @@ -212,6 +213,7 @@ iOS WiFi: - 使用 `NEHotspotConfiguration`。 - 插件会向 Cordova iOS entitlements 写入 `com.apple.developer.networking.HotspotConfiguration`,但仍必须在 Apple Developer App ID 中开启 `Hotspot Configuration` capability,并使用包含该 capability 的 provisioning profile 重新构建。 +- iOS 调用会在主线程执行。默认会先查询本 App 已配置的 SSID,如果同名 SSID 存在,会先移除该 App 写入的旧配置再重新应用,以规避 iOS 内部状态导致的 `NEHotspotConfigurationErrorInternal`。 - 如果 iOS 返回 `NEHotspotConfigurationErrorDomain` code `8` / `internal error`,优先检查 App ID、entitlements、provisioning profile 是否都包含 `Hotspot Configuration`,并确认是在真机前台运行,不是在模拟器中测试。 - iOS 的 `NEHotspotConfiguration` password initializer 公开支持 WEP/WPA/WPA2 Personal。WPA2/WPA3 mixed 通常应先按 `security: "wpa2"` 测试;如果 WPA2-only 可用但 mixed 不可用,需要调整热点兼容性配置或改用配置描述文件/MDM 流程。 - iOS 不提供第三方 App 断开当前 WiFi 的公开 API,因此 `disconnectWifi` 在 iOS 返回错误。 diff --git a/package.json b/package.json index 5d6d619..072a09f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bluememe-utils", - "version": "0.1.2", + "version": "0.1.3", "description": "Cordova utilities for WiFi, Firebase Cloud Messaging, and biometric unlock for OutSystems 11 apps.", "license": "MIT", "files": [ diff --git a/plugin.xml b/plugin.xml index 0aecb06..0ce47b5 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="0.1.3"> bluememe-utils Cordova utilities for WiFi, Firebase Cloud Messaging, and biometric unlock. MIT diff --git a/src/ios/CDVBluememeUtils.m b/src/ios/CDVBluememeUtils.m index c6395d4..3465228 100644 --- a/src/ios/CDVBluememeUtils.m +++ b/src/ios/CDVBluememeUtils.m @@ -282,6 +282,13 @@ static NSString * const BluememeDeviceKeyDefaultsKey = @"bluememe_utils_device_k - (void)applyWifiCommand:(CDVInvokedUrlCommand *)command defaultJoinOnce:(BOOL)defaultJoinOnce { + if (![NSThread isMainThread]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self applyWifiCommand:command defaultJoinOnce:defaultJoinOnce]; + }); + return; + } + NSDictionary *options = [self firstDictionaryArgument:command]; NSString *ssid = [self stringFromDictionary:options key:@"ssid"]; NSString *password = [self stringFromDictionary:options key:@"password"]; @@ -293,6 +300,7 @@ static NSString * const BluememeDeviceKeyDefaultsKey = @"bluememe_utils_device_k BOOL isWep = [security isEqualToString:@"wep"] || [self boolFromDictionary:options key:@"isWep" defaultValue:NO]; BOOL joinOnce = [self boolFromDictionary:options key:@"joinOnce" defaultValue:defaultJoinOnce]; BOOL hiddenSsid = [self boolFromDictionary:options key:@"hiddenSsid" defaultValue:NO]; + BOOL resetExisting = [self boolFromDictionary:options key:@"resetExisting" defaultValue:YES]; if (ssid.length == 0) { [self sendErrorForCommand:command code:@"INVALID_WIFI_OPTIONS" message:@"ssid is required."]; @@ -317,45 +325,117 @@ static NSString * const BluememeDeviceKeyDefaultsKey = @"bluememe_utils_device_k #endif if (@available(iOS 11.0, *)) { - NEHotspotConfiguration *configuration = nil; - if (isOpen) { - configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid]; - } else { - configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid passphrase:password isWEP:isWep]; - } - if (@available(iOS 13.0, *)) { - configuration.hidden = hiddenSsid; - } - configuration.joinOnce = joinOnce; - - [[NEHotspotConfigurationManager sharedManager] applyConfiguration:configuration completionHandler:^(NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (error != nil && error.code != NEHotspotConfigurationErrorAlreadyAssociated) { - NSDictionary *context = @{ - @"ssid": ssid, - @"security": security, - @"passwordLength": @(password.length), - @"joinOnce": @(joinOnce), - @"hiddenSsid": @(hiddenSsid) - }; - [self sendWifiApplyErrorForCommand:command error:error context:context]; - return; - } - NSDictionary *result = @{ - @"ssid": ssid, - @"platformMode": @"hotspotConfiguration", - @"connected": @YES, - @"saved": @(!joinOnce), - @"alreadyAssociated": @(error != nil && error.code == NEHotspotConfigurationErrorAlreadyAssociated) - }; - [self sendSuccessForCommand:command dictionary:result]; - }); - }]; + NSDictionary *context = @{ + @"ssid": ssid, + @"security": security, + @"passwordLength": @(password.length), + @"joinOnce": @(joinOnce), + @"hiddenSsid": @(hiddenSsid), + @"resetExisting": @(resetExisting) + }; + [self prepareAndApplyWifiConfigurationForCommand:command + ssid:ssid + password:password + isOpen:isOpen + isWep:isWep + joinOnce:joinOnce + hiddenSsid:hiddenSsid + resetExisting:resetExisting + context:context]; } else { [self sendErrorForCommand:command code:@"UNSUPPORTED_IOS_VERSION" message:@"WiFi configuration requires iOS 11 or newer."]; } } +- (void)prepareAndApplyWifiConfigurationForCommand:(CDVInvokedUrlCommand *)command + ssid:(NSString *)ssid + password:(NSString *)password + isOpen:(BOOL)isOpen + isWep:(BOOL)isWep + joinOnce:(BOOL)joinOnce + hiddenSsid:(BOOL)hiddenSsid + resetExisting:(BOOL)resetExisting + context:(NSDictionary *)context API_AVAILABLE(ios(11.0)) +{ + NEHotspotConfigurationManager *manager = [NEHotspotConfigurationManager sharedManager]; + if (!resetExisting) { + [self applyWifiConfigurationForCommand:command + manager:manager + ssid:ssid + password:password + isOpen:isOpen + isWep:isWep + joinOnce:joinOnce + hiddenSsid:hiddenSsid + context:context]; + return; + } + + [manager getConfiguredSSIDsWithCompletionHandler:^(NSArray *configuredSSIDs) { + BOOL configuredByApp = [configuredSSIDs containsObject:ssid]; + NSMutableDictionary *updatedContext = [context mutableCopy]; + updatedContext[@"configuredByApp"] = @(configuredByApp); + + void (^apply)(void) = ^{ + [self applyWifiConfigurationForCommand:command + manager:manager + ssid:ssid + password:password + isOpen:isOpen + isWep:isWep + joinOnce:joinOnce + hiddenSsid:hiddenSsid + context:updatedContext]; + }; + + if (configuredByApp) { + [manager removeConfigurationForSSID:ssid]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(500 * NSEC_PER_MSEC)), dispatch_get_main_queue(), apply); + } else { + dispatch_async(dispatch_get_main_queue(), apply); + } + }]; +} + +- (void)applyWifiConfigurationForCommand:(CDVInvokedUrlCommand *)command + manager:(NEHotspotConfigurationManager *)manager + ssid:(NSString *)ssid + password:(NSString *)password + isOpen:(BOOL)isOpen + isWep:(BOOL)isWep + joinOnce:(BOOL)joinOnce + hiddenSsid:(BOOL)hiddenSsid + context:(NSDictionary *)context API_AVAILABLE(ios(11.0)) +{ + NEHotspotConfiguration *configuration = nil; + if (isOpen) { + configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid]; + } else { + configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid passphrase:password isWEP:isWep]; + } + if (@available(iOS 13.0, *)) { + configuration.hidden = hiddenSsid; + } + configuration.joinOnce = joinOnce; + + [manager applyConfiguration:configuration completionHandler:^(NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error != nil && error.code != NEHotspotConfigurationErrorAlreadyAssociated) { + [self sendWifiApplyErrorForCommand:command error:error context:context]; + return; + } + NSDictionary *result = @{ + @"ssid": ssid, + @"platformMode": @"hotspotConfiguration", + @"connected": @YES, + @"saved": @(!joinOnce), + @"alreadyAssociated": @(error != nil && error.code == NEHotspotConfigurationErrorAlreadyAssociated) + }; + [self sendSuccessForCommand:command dictionary:result]; + }); + }]; +} + - (void)sendWifiApplyErrorForCommand:(CDVInvokedUrlCommand *)command error:(NSError *)error context:(NSDictionary *)context { NSMutableDictionary *details = [NSMutableDictionary dictionary];