Compare commits
No commits in common. "4a2636879d9dacf5354632174fd8ed37b0c3218a" and "24f8ef9c55bdc23edc5d85ef10efbaaf77671963" have entirely different histories.
4a2636879d
...
24f8ef9c55
159
index2.html
159
index2.html
@ -1,159 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Socket.IO Test Client</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; display: flex; flex-direction: column; max-width: 800px; margin: auto; }
|
||||
.controls, .message-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; border-radius: 5px; }
|
||||
input[type="text"] { width: calc(100% - 100px); padding: 8px; margin-right: 5px; }
|
||||
button { padding: 8px 12px; }
|
||||
#messages { border: 1px solid #eee; padding: 10px; height: 300px; overflow-y: scroll; background-color: #f9f9f9; }
|
||||
.msg { margin: 5px 0; padding: 5px; border-radius: 3px; }
|
||||
.server-msg { background-color: #e1f5fe; }
|
||||
.client-msg { background-color: #c8e6c9; text-align: right; }
|
||||
.status-msg { background-color: #fff9c4; text-align: center; font-style: italic; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Socket.IO Test Client</h1>
|
||||
|
||||
<div class="controls">
|
||||
<h3>Подключение</h3>
|
||||
<input type="text" id="url" placeholder="Сервер URL" value="https://127.0.0.1:5205/">
|
||||
<input type="text" id="token" placeholder="Токен (для auth)" value="my-secret-token-123">
|
||||
<button id="connectBtn">Подключиться</button>
|
||||
<button id="disconnectBtn" disabled>Отключиться</button>
|
||||
</div>
|
||||
|
||||
<div class="message-box">
|
||||
<h3>Отправка клиентского сообщения</h3>
|
||||
<input type="text" id="message" placeholder="Введите сообщение" disabled>
|
||||
<button id="sendBtn" disabled>Отправить</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<h3>Настройки</h3>
|
||||
<label>
|
||||
<input type="checkbox" id="invertLogs">
|
||||
Новый сверху (инвертировать лог)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h3>Логи <button id="clearLogBtn" style="font-size: 0.8em; float: right;">Очистить</button></h3>
|
||||
<div id="messages"></div>
|
||||
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
<script>
|
||||
const urlInput = document.getElementById('url');
|
||||
const tokenInput = document.getElementById('token');
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
const disconnectBtn = document.getElementById('disconnectBtn');
|
||||
const messageInput = document.getElementById('message');
|
||||
const sendBtn = document.getElementById('sendBtn');
|
||||
const messagesDiv = document.getElementById('messages');
|
||||
const clearLogBtn = document.getElementById('clearLogBtn');
|
||||
const invertLogsCheckbox = document.getElementById('invertLogs');
|
||||
|
||||
let socket = null;
|
||||
|
||||
function logMessage(message, type = 'status') {
|
||||
const ts = new Date().toLocaleTimeString();
|
||||
const newLogEntry = `<div class="msg ${type}-msg">[${ts}] ${message}</div>`;
|
||||
if (invertLogsCheckbox.checked) {
|
||||
messagesDiv.innerHTML = newLogEntry + messagesDiv.innerHTML;
|
||||
messagesDiv.scrollTop = 0;
|
||||
} else {
|
||||
messagesDiv.innerHTML += newLogEntry;
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function connectToServer() {
|
||||
if (socket) {
|
||||
logMessage('Уже подключены. Сначала отключитесь.', 'status');
|
||||
return;
|
||||
}
|
||||
const token = tokenInput.value;
|
||||
const url = urlInput.value || 'https://127.0.0.1:5205/';
|
||||
logMessage('Подключаемся к серверу...', 'status');
|
||||
|
||||
socket = io(url, {
|
||||
auth: {
|
||||
token: token || null
|
||||
}
|
||||
});
|
||||
|
||||
// Универсальный перехват всех событий
|
||||
socket.onAny((event, ...args) => {
|
||||
try {
|
||||
logMessage(`onAny -> ${event}: ${JSON.stringify(args[0])}`, 'server');
|
||||
} catch (e) {
|
||||
logMessage(`onAny -> ${event}: <unserializable>`, 'server');
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
logMessage(`Успешное подключение! SID: ${socket.id}`, 'status');
|
||||
connectBtn.disabled = true;
|
||||
disconnectBtn.disabled = false;
|
||||
messageInput.disabled = false;
|
||||
sendBtn.disabled = false;
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
logMessage('Отключено от сервера.', 'status');
|
||||
connectBtn.disabled = false;
|
||||
disconnectBtn.disabled = true;
|
||||
messageInput.disabled = true;
|
||||
sendBtn.disabled = true;
|
||||
socket = null;
|
||||
});
|
||||
|
||||
// Часто используемые события
|
||||
socket.on('message', (data) => {
|
||||
logMessage(`message: ${JSON.stringify(data)}`, 'server');
|
||||
});
|
||||
socket.on('connected', (data) => {
|
||||
logMessage(`connected: ${JSON.stringify(data)}`, 'server');
|
||||
});
|
||||
socket.on('chat:new_message', (data) => {
|
||||
logMessage(`chat:new_message: ${JSON.stringify(data)}`, 'server');
|
||||
});
|
||||
socket.on('achievement:received', (data) => {
|
||||
logMessage(`achievement:received: ${JSON.stringify(data)}`, 'server');
|
||||
});
|
||||
socket.on('achievement:progress', (data) => {
|
||||
logMessage(`achievement:progress: ${JSON.stringify(data)}`, 'server');
|
||||
});
|
||||
|
||||
socket.on('connect_error', (err) => {
|
||||
logMessage(`Ошибка подключения: ${err.message}`, 'status');
|
||||
socket = null;
|
||||
});
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
const message = messageInput.value;
|
||||
if (socket && message) {
|
||||
socket.emit('client_message', { data: message });
|
||||
logMessage(`Я: ${message}`, 'client');
|
||||
messageInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
connectBtn.addEventListener('click', connectToServer);
|
||||
disconnectBtn.addEventListener('click', () => {
|
||||
if (socket) socket.disconnect();
|
||||
});
|
||||
sendBtn.addEventListener('click', sendMessage);
|
||||
messageInput.addEventListener('keyup', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
clearLogBtn.addEventListener('click', () => {
|
||||
messagesDiv.innerHTML = '';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -6,10 +6,6 @@
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1A85C6CC2EA6FD73009FA847 /* SocketIO in Frameworks */ = {isa = PBXBuildFile; productRef = 1A85C6CB2EA6FD73009FA847 /* SocketIO */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
1A6D61DB2E7CD04000B9F736 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
@ -56,7 +52,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1A85C6CC2EA6FD73009FA847 /* SocketIO in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -117,7 +112,6 @@
|
||||
);
|
||||
name = yobble;
|
||||
packageProductDependencies = (
|
||||
1A85C6CB2EA6FD73009FA847 /* SocketIO */,
|
||||
);
|
||||
productName = yobble;
|
||||
productReference = 1A6D61CC2E7CD03E00B9F736 /* yobble.app */;
|
||||
@ -202,9 +196,6 @@
|
||||
);
|
||||
mainGroup = 1A6D61C32E7CD03E00B9F736;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
packageReferences = (
|
||||
1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 1A6D61CD2E7CD03E00B9F736 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -607,25 +598,6 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/socketio/socket.io-client-swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 16.1.1;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
1A85C6CB2EA6FD73009FA847 /* SocketIO */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */;
|
||||
productName = SocketIO;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 1A6D61C42E7CD03E00B9F736 /* Project object */;
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
{
|
||||
"originHash" : "c9fb241c5f575df8f20b39649006995779013948e60c51c3f85b729f83b054e7",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "socket.io-client-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/socketio/socket.io-client-swift",
|
||||
"state" : {
|
||||
"revision" : "42da871d9369f290d6ec4930636c40672143905b",
|
||||
"version" : "16.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream",
|
||||
"state" : {
|
||||
"revision" : "c6bfd1af48efcc9a9ad203665db12375ba6b145a",
|
||||
"version" : "4.0.8"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension Notification.Name {
|
||||
static let accessTokenDidChange = Notification.Name("accessTokenDidChange")
|
||||
}
|
||||
@ -69,8 +69,6 @@ final class AuthService {
|
||||
}
|
||||
UserDefaults.standard.set(username, forKey: "currentUser")
|
||||
|
||||
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
|
||||
|
||||
completion(true, nil)
|
||||
} catch {
|
||||
completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
|
||||
@ -177,8 +175,6 @@ final class AuthService {
|
||||
|
||||
UserDefaults.standard.removeObject(forKey: "currentUser")
|
||||
|
||||
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
|
||||
|
||||
let allUsers = KeychainService.shared.getAllServices()
|
||||
for user in allUsers {
|
||||
let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
|
||||
@ -187,7 +183,6 @@ final class AuthService {
|
||||
if hasAccessToken && hasRefreshToken {
|
||||
UserDefaults.standard.set(user, forKey: "currentUser")
|
||||
if AppConfig.DEBUG { print("Logout: переключились на пользователя \(user)") }
|
||||
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
|
||||
completion(true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -266,8 +266,6 @@ final class NetworkClient {
|
||||
KeychainService.shared.save(userId, forKey: "userId", service: tokenInfo.login)
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
|
||||
|
||||
self.completeRefresh(success: true)
|
||||
} catch {
|
||||
self.completeRefresh(success: false)
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
import Foundation
|
||||
#if canImport(SocketIO)
|
||||
import SocketIO
|
||||
#endif
|
||||
|
||||
final class SocketService {
|
||||
static let shared = SocketService()
|
||||
|
||||
private let syncQueue = DispatchQueue(label: "org.yobble.socket.service")
|
||||
private var currentToken: String?
|
||||
private var currentAuthPayload: [String: Any] = [:]
|
||||
|
||||
#if canImport(SocketIO)
|
||||
private var manager: SocketManager?
|
||||
private var socket: SocketIOClient?
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
func connectForCurrentUser() {
|
||||
syncQueue.async { [weak self] in
|
||||
guard let self else { return }
|
||||
guard let token = self.resolveCurrentAccessToken() else {
|
||||
if AppConfig.DEBUG { print("[SocketService] No access token available, disconnecting") }
|
||||
self.currentToken = nil
|
||||
self.disconnectInternal()
|
||||
return
|
||||
}
|
||||
|
||||
self.connectInternal(with: token)
|
||||
}
|
||||
}
|
||||
|
||||
func connect(withToken token: String) {
|
||||
syncQueue.async { [weak self] in
|
||||
self?.connectInternal(with: token)
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
syncQueue.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.currentToken = nil
|
||||
self.disconnectInternal()
|
||||
}
|
||||
}
|
||||
|
||||
private func resolveCurrentAccessToken() -> String? {
|
||||
guard
|
||||
let login = UserDefaults.standard.string(forKey: "currentUser"),
|
||||
!login.isEmpty
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return KeychainService.shared.get(forKey: "access_token", service: login)
|
||||
}
|
||||
|
||||
private func connectInternal(with token: String) {
|
||||
#if canImport(SocketIO)
|
||||
if token == currentToken,
|
||||
let socket,
|
||||
socket.status == .connected || socket.status == .connecting {
|
||||
if AppConfig.DEBUG { print("[SocketService] Already connected with current token") }
|
||||
return
|
||||
}
|
||||
|
||||
currentToken = token
|
||||
currentAuthPayload = ["token": token]
|
||||
setupSocket(with: token)
|
||||
socket?.connect(withPayload: currentAuthPayload)
|
||||
#else
|
||||
if AppConfig.DEBUG {
|
||||
print("[SocketService] SocketIO framework not available; skipping connection")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if canImport(SocketIO)
|
||||
private func setupSocket(with token: String) {
|
||||
guard let baseURL = URL(string: AppConfig.API_SERVER) else {
|
||||
if AppConfig.DEBUG { print("[SocketService] Invalid socket URL: \(AppConfig.API_SERVER)") }
|
||||
return
|
||||
}
|
||||
|
||||
disconnectInternal()
|
||||
|
||||
let configuration: SocketIOClientConfiguration = [
|
||||
.log(AppConfig.DEBUG),
|
||||
.compress,
|
||||
.secure(AppConfig.PROTOCOL.lowercased() == "https"),
|
||||
.path(AppConfig.SOCKET_PATH),
|
||||
.reconnects(true),
|
||||
.reconnectWait(2),
|
||||
.forceWebsockets(true),
|
||||
.extraHeaders([
|
||||
"Authorization": "Bearer \(token)",
|
||||
"User-Agent": AppConfig.USER_AGENT
|
||||
]),
|
||||
.connectParams(["token": token])
|
||||
]
|
||||
|
||||
let manager = SocketManager(socketURL: baseURL, config: configuration)
|
||||
manager.handleQueue = syncQueue
|
||||
let socket = manager.defaultSocket
|
||||
|
||||
if AppConfig.DEBUG {
|
||||
socket.onAny { event in
|
||||
print("[SocketService] onAny event=\(event.event) data=\(event.items ?? [])")
|
||||
}
|
||||
}
|
||||
|
||||
socket.on(clientEvent: .connect) { _, _ in
|
||||
if AppConfig.DEBUG { print("[SocketService] Connected") }
|
||||
}
|
||||
|
||||
socket.on(clientEvent: .disconnect) { data, _ in
|
||||
if AppConfig.DEBUG { print("[SocketService] Disconnected: \(data)") }
|
||||
}
|
||||
|
||||
socket.on(clientEvent: .error) { data, _ in
|
||||
if AppConfig.DEBUG { print("[SocketService] Error: \(data)") }
|
||||
}
|
||||
|
||||
self.manager = manager
|
||||
self.socket = socket
|
||||
}
|
||||
|
||||
private func disconnectInternal() {
|
||||
socket?.disconnect()
|
||||
manager?.disconnect()
|
||||
socket = nil
|
||||
manager = nil
|
||||
}
|
||||
#else
|
||||
private func disconnectInternal() { }
|
||||
#endif
|
||||
}
|
||||
@ -18,7 +18,6 @@ class LoginViewModel: ObservableObject {
|
||||
@Published var isLoggedIn: Bool = false
|
||||
|
||||
private let authService = AuthService()
|
||||
private let socketService = SocketService.shared
|
||||
|
||||
private enum DefaultsKeys {
|
||||
static let currentUser = "currentUser"
|
||||
@ -39,12 +38,10 @@ class LoginViewModel: ObservableObject {
|
||||
if success {
|
||||
self?.loadStoredUser()
|
||||
self?.isLoggedIn = true
|
||||
self?.socketService.connectForCurrentUser()
|
||||
} else {
|
||||
self?.isLoggedIn = false
|
||||
self?.errorMessage = error ?? NSLocalizedString("Произошла ошибка.", comment: "")
|
||||
self?.showError = false
|
||||
self?.socketService.disconnect()
|
||||
}
|
||||
self?.isLoading = false
|
||||
}
|
||||
@ -62,11 +59,9 @@ class LoginViewModel: ObservableObject {
|
||||
if success {
|
||||
self?.loadStoredUser()
|
||||
self?.isLoggedIn = true
|
||||
self?.socketService.connectForCurrentUser()
|
||||
} else {
|
||||
self?.errorMessage = error ?? NSLocalizedString("Неизвестная ошибка", comment: "")
|
||||
self?.showError = true
|
||||
self?.socketService.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,9 +73,6 @@ class LoginViewModel: ObservableObject {
|
||||
if success {
|
||||
self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
|
||||
self?.loadStoredUser()
|
||||
self?.socketService.connectForCurrentUser()
|
||||
} else {
|
||||
self?.socketService.disconnect()
|
||||
}
|
||||
completion(success, message)
|
||||
}
|
||||
@ -95,7 +87,6 @@ class LoginViewModel: ObservableObject {
|
||||
self?.password = ""
|
||||
self?.isLoggedIn = true
|
||||
self?.showError = false
|
||||
self?.socketService.connectForCurrentUser()
|
||||
} else {
|
||||
self?.username = ""
|
||||
self?.userId = ""
|
||||
@ -103,7 +94,6 @@ class LoginViewModel: ObservableObject {
|
||||
self?.isLoggedIn = false
|
||||
self?.errorMessage = error ?? NSLocalizedString("Ошибка при деавторизации.", comment: "")
|
||||
self?.showError = false
|
||||
self?.socketService.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ struct AppConfig {
|
||||
//static let SERVICE = Bundle.main.bundleIdentifier ?? "default.service"
|
||||
static let PROTOCOL = "https"
|
||||
static let API_SERVER = "\(PROTOCOL)://api.yobble.org"
|
||||
static let SOCKET_PATH = "/socket.io/"
|
||||
|
||||
static let USER_AGENT = "yobble ios"
|
||||
static let APP_BUILD = "appstore" // appstore / freestore
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user