From 92735af95587cca89f944d765cb50fb0776a7805 Mon Sep 17 00:00:00 2001 From: "aria.ark" Date: Fri, 3 Apr 2026 12:40:43 +0900 Subject: [PATCH] Update iOS debugging info --- README.md | 20 ++-- src/ios/BluememeWifiConnector.m | 199 ++++++++++++++++++++++++++++---- 2 files changed, 186 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f78e8d2..ed33699 100644 --- a/README.md +++ b/README.md @@ -33,21 +33,23 @@ 插件加载后可通过 `cordova.plugins.BluememeWifiConnector` 调用。 ```javascript -document.addEventListener("deviceready", function () { - cordova.plugins.BluememeWifiConnector.connect({ - ssid: "Factory_AP_01", - passphrase: "12345678", +document.addEventListener("deviceready", function () { + cordova.plugins.BluememeWifiConnector.connect({ + ssid: "Factory_AP_01", + passphrase: "12345678", security: "WPA2", mode: "auto" }).then(function (result) { console.log("Wi-Fi success", result); }).catch(function (error) { console.error("Wi-Fi error", error); - }); -}); -``` - -## 推荐模式 + }); +}); +``` + +iOS 成功时会额外返回 `connectionSucceeded`、`connectionStatus` 和 `details`。其中 `details` 会包含 `configuredSSIDs`、`requestedSSIDConfiguredByApp`、`passphraseLength` 等诊断字段;失败时同样会把输入摘要和 native 错误信息放进 `error.details`,便于定位是参数问题还是系统拒绝。 + +## 推荐模式 ### 1. 设备级保存网络 diff --git a/src/ios/BluememeWifiConnector.m b/src/ios/BluememeWifiConnector.m index 8f8e913..d67396e 100644 --- a/src/ios/BluememeWifiConnector.m +++ b/src/ios/BluememeWifiConnector.m @@ -23,7 +23,14 @@ } NSString *message = [NSString stringWithFormat:@"Mode '%@' is not supported on iOS. Use 'save' or 'localOnly'.", resolvedMode]; - [self sendErrorCode:@"MODE_NOT_SUPPORTED" message:message details:nil command:command]; + [self sendErrorCode:@"MODE_NOT_SUPPORTED" + message:message + details:@{ + @"platform": @"ios", + @"requestedMode": requestedMode ?: @"", + @"resolvedMode": resolvedMode ?: @"" + } + command:command]; } - (void)connectLocalOnly:(CDVInvokedUrlCommand *)command @@ -101,15 +108,22 @@ { if (@available(iOS 11.0, *)) { NSString *ssid = [self stringForKey:@"ssid" inOptions:options]; + NSString *security = [self normalizeSecurity:[self stringForKey:@"security" inOptions:options]]; + NSMutableDictionary *requestDetails = [self requestDetailsForOptions:options + ssid:ssid + security:security + mode:mode + joinOnce:joinOnce]; + if (ssid.length == 0) { + requestDetails[@"invalidOption"] = @"ssid"; [self sendErrorCode:@"INVALID_OPTIONS" message:@"Missing required option: ssid" - details:nil + details:requestDetails command:command]; return; } - NSString *security = [self normalizeSecurity:[self stringForKey:@"security" inOptions:options]]; NEHotspotConfiguration *configuration = nil; if ([security isEqualToString:@"OPEN"]) { @@ -117,23 +131,36 @@ } else if ([security isEqualToString:@"WPA2"]) { NSString *passphrase = [self stringForKey:@"passphrase" inOptions:options]; if (passphrase.length == 0) { + requestDetails[@"invalidOption"] = @"passphrase"; [self sendErrorCode:@"INVALID_OPTIONS" message:@"Missing required option: passphrase" - details:nil + details:requestDetails + command:command]; + return; + } + + if (passphrase.length < 8 || passphrase.length > 63) { + requestDetails[@"invalidOption"] = @"passphrase"; + requestDetails[@"validation"] = @"WPA2 passphrase must be between 8 and 63 characters."; + [self sendErrorCode:@"INVALID_OPTIONS" + message:@"Invalid passphrase length for WPA2 Wi-Fi." + details:requestDetails command:command]; return; } configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid passphrase:passphrase isWEP:NO]; } else if ([security isEqualToString:@"WPA3"]) { + requestDetails[@"invalidOption"] = @"security"; [self sendErrorCode:@"MODE_NOT_SUPPORTED" message:@"iOS hotspot configuration supports OPEN, WEP, WPA, and WPA2. WPA3 is not exposed by this plugin on iOS." - details:nil + details:requestDetails command:command]; return; } else { NSString *message = [NSString stringWithFormat:@"Unsupported security type on iOS: %@", security]; - [self sendErrorCode:@"INVALID_SECURITY" message:message details:nil command:command]; + requestDetails[@"invalidOption"] = @"security"; + [self sendErrorCode:@"INVALID_SECURITY" message:message details:requestDetails command:command]; return; } @@ -142,26 +169,24 @@ dispatch_async(dispatch_get_main_queue(), ^{ [[NEHotspotConfigurationManager sharedManager] applyConfiguration:configuration completionHandler:^(NSError * _Nullable error) { - if (error != nil) { - [self sendNativeError:error code:@"IOS_WIFI_CONFIGURATION_FAILED" command:command]; + if (error != nil && ![self isAlreadyAssociatedError:error]) { + [self sendNativeError:error + code:@"IOS_WIFI_CONFIGURATION_FAILED" + requestDetails:requestDetails + command:command]; return; } - NSMutableDictionary *result = [@{ - @"mode": mode, - @"ssid": ssid, - @"apiName": @"NEHotspotConfigurationManager", - @"platform": @"ios", - @"joinOnce": @(joinOnce), - @"hidden": @(configuration.hidden), - @"saved": @(!joinOnce) - } mutableCopy]; - - if ([mode isEqualToString:@"localOnly"]) { - result[@"boundToProcess"] = @NO; - } - - [self sendSuccess:result command:command]; + NSString *connectionStatus = error == nil ? @"APPLIED" : @"ALREADY_ASSOCIATED"; + [self sendApplyConfigurationSuccessForSSID:ssid + security:security + mode:mode + joinOnce:joinOnce + configuration:configuration + requestDetails:requestDetails + connectionStatus:connectionStatus + nativeError:error + command:command]; }]; }); return; @@ -173,6 +198,99 @@ command:command]; } +- (NSMutableDictionary *)requestDetailsForOptions:(NSDictionary *)options + ssid:(NSString *)ssid + security:(NSString *)security + mode:(NSString *)mode + joinOnce:(BOOL)joinOnce +{ + NSString *passphrase = [self stringForKey:@"passphrase" inOptions:options]; + BOOL hidden = [self boolForKey:@"hidden" inOptions:options defaultValue:NO]; + NSMutableDictionary *details = [@{ + @"platform": @"ios", + @"apiName": @"NEHotspotConfigurationManager", + @"resolvedMode": mode ?: @"", + @"security": security ?: @"", + @"joinOnce": @(joinOnce), + @"hidden": @(hidden), + @"ssidLength": @(ssid.length), + @"passphraseProvided": @(passphrase.length > 0), + @"systemVersion": [[NSProcessInfo processInfo] operatingSystemVersionString] ?: @"" + } mutableCopy]; + + if (passphrase.length > 0) { + details[@"passphraseLength"] = @(passphrase.length); + } + + if (options[@"localOnly"] != nil) { + details[@"localOnly"] = @([self boolForKey:@"localOnly" inOptions:options defaultValue:NO]); + } + + return details; +} + +- (NSMutableDictionary *)baseConnectionResultForSSID:(NSString *)ssid + security:(NSString *)security + mode:(NSString *)mode + joinOnce:(BOOL)joinOnce + configuration:(NEHotspotConfiguration *)configuration + requestDetails:(NSDictionary *)requestDetails + connectionStatus:(NSString *)connectionStatus +{ + NSMutableDictionary *result = [@{ + @"mode": mode ?: @"", + @"ssid": ssid ?: @"", + @"security": security ?: @"", + @"apiName": @"NEHotspotConfigurationManager", + @"platform": @"ios", + @"joinOnce": @(joinOnce), + @"hidden": @(configuration.hidden), + @"saved": @(!joinOnce), + @"connectionSucceeded": @YES, + @"connectionStatus": connectionStatus ?: @"UNKNOWN", + @"details": [requestDetails mutableCopy] + } mutableCopy]; + + if ([mode isEqualToString:@"localOnly"]) { + result[@"boundToProcess"] = @NO; + } + + return result; +} + +- (void)sendApplyConfigurationSuccessForSSID:(NSString *)ssid + security:(NSString *)security + mode:(NSString *)mode + joinOnce:(BOOL)joinOnce + configuration:(NEHotspotConfiguration *)configuration + requestDetails:(NSDictionary *)requestDetails + connectionStatus:(NSString *)connectionStatus + nativeError:(NSError * _Nullable)nativeError + command:(CDVInvokedUrlCommand *)command API_AVAILABLE(ios(11.0)) +{ + NSMutableDictionary *result = [self baseConnectionResultForSSID:ssid + security:security + mode:mode + joinOnce:joinOnce + configuration:configuration + requestDetails:requestDetails + connectionStatus:connectionStatus]; + NSMutableDictionary *details = [result[@"details"] mutableCopy]; + details[@"configurationApplied"] = @YES; + + if (nativeError != nil) { + [details addEntriesFromDictionary:[self nativeErrorDetails:nativeError]]; + } + + [[NEHotspotConfigurationManager sharedManager] getConfiguredSSIDsWithCompletionHandler:^(NSArray * _Nonnull ssids) { + NSArray *configuredSsids = ssids ?: @[]; + details[@"configuredSSIDs"] = configuredSsids; + details[@"requestedSSIDConfiguredByApp"] = @([configuredSsids containsObject:ssid]); + result[@"details"] = details; + [self sendSuccess:result command:command]; + }]; +} + - (NSDictionary *)optionsFromCommand:(CDVInvokedUrlCommand *)command { if ([command.arguments count] == 0) { @@ -279,19 +397,52 @@ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -- (void)sendNativeError:(NSError *)error code:(NSString *)code command:(CDVInvokedUrlCommand *)command +- (NSDictionary *)nativeErrorDetails:(NSError *)error { NSMutableDictionary *details = [NSMutableDictionary dictionary]; if (error.domain.length > 0) { details[@"domain"] = error.domain; } details[@"nativeCode"] = @(error.code); + if (error.localizedDescription.length > 0) { + details[@"nativeMessage"] = error.localizedDescription; + } id failureReason = error.userInfo[NSLocalizedFailureReasonErrorKey]; if ([failureReason isKindOfClass:[NSString class]] && [failureReason length] > 0) { details[@"failureReason"] = failureReason; } + id recoverySuggestion = error.userInfo[NSLocalizedRecoverySuggestionErrorKey]; + if ([recoverySuggestion isKindOfClass:[NSString class]] && [recoverySuggestion length] > 0) { + details[@"recoverySuggestion"] = recoverySuggestion; + } + + id debugDescription = error.userInfo[NSDebugDescriptionErrorKey]; + if ([debugDescription isKindOfClass:[NSString class]] && [debugDescription length] > 0) { + details[@"debugDescription"] = debugDescription; + } + + return details; +} + +- (BOOL)isAlreadyAssociatedError:(NSError *)error +{ + return error != nil && + [error.domain isEqualToString:NEHotspotConfigurationErrorDomain] && + error.code == NEHotspotConfigurationErrorAlreadyAssociated; +} + +- (void)sendNativeError:(NSError *)error + code:(NSString *)code + requestDetails:(NSDictionary *)requestDetails + command:(CDVInvokedUrlCommand *)command +{ + NSMutableDictionary *details = [requestDetails mutableCopy] ?: [NSMutableDictionary dictionary]; + [details addEntriesFromDictionary:[self nativeErrorDetails:error]]; + details[@"connectionSucceeded"] = @NO; + details[@"connectionStatus"] = @"FAILED"; + [self sendErrorCode:code message:error.localizedDescription details:details