6#include <emscripten/bind.h>
7#include <emscripten/val.h>
14#include "absl/strings/str_format.h"
16#include "nlohmann/json.hpp"
18using json = nlohmann::json;
21EM_JS(
double, GetCurrentTime, (), {
22 return Date.now() / 1000.0;
25EM_JS(
void, ConsoleLog, (
const char* message), {
26 console.log(
'[WasmCollaboration] ' + UTF8ToString(message));
29EM_JS(
void, ConsoleError, (
const char* message), {
30 console.error(
'[WasmCollaboration] ' + UTF8ToString(message));
33EM_JS(
void, UpdateCollaborationUI, (
const char* type,
const char* data), {
34 if (typeof window.updateCollaborationUI ===
'function') {
35 window.updateCollaborationUI(UTF8ToString(type), UTF8ToString(data));
39EM_JS(
char*, GenerateRandomRoomCode, (), {
40 var chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
42 for (var i = 0; i < 6; i++) {
43 result += chars.charAt(Math.floor(Math.random() * chars.length));
45 var lengthBytes = lengthBytesUTF8(result) + 1;
46 var stringOnWasmHeap = _malloc(lengthBytes);
47 stringToUTF8(result, stringOnWasmHeap, lengthBytes);
48 return stringOnWasmHeap;
51EM_JS(
char*, GetCollaborationServerUrl, (), {
58 if (typeof window !==
'undefined') {
59 if (window.YAZE_CONFIG && window.YAZE_CONFIG.collaborationServerUrl) {
60 url = window.YAZE_CONFIG.collaborationServerUrl;
63 var meta = document.querySelector(
'meta[name="yaze-collab-server"]');
64 if (meta && meta.content) {
70 if (url.length === 0) {
74 var lengthBytes = lengthBytesUTF8(url) + 1;
75 var stringOnWasmHeap = _malloc(lengthBytes);
76 stringToUTF8(url, stringOnWasmHeap, lengthBytes);
77 return stringOnWasmHeap;
88const std::vector<std::string> kUserColors = {
99WasmCollaboration& GetInstance() {
100 static WasmCollaboration instance;
106WasmCollaboration& GetWasmCollaborationInstance() {
107 return GetInstance();
110WasmCollaboration::WasmCollaboration() {
111 user_id_ = GenerateUserId();
112 user_color_ = GenerateUserColor();
113 websocket_ = std::make_unique<net::EmscriptenWebSocket>();
116 InitializeFromConfig();
119WasmCollaboration::~WasmCollaboration() {
125void WasmCollaboration::InitializeFromConfig() {
126 char* url = GetCollaborationServerUrl();
127 if (url !=
nullptr) {
128 websocket_url_ = std::string(url);
130 ConsoleLog((
"Collaboration server configured: " + websocket_url_).c_str());
133 "Collaboration server not configured. Set "
134 "window.YAZE_CONFIG.collaborationServerUrl or add <meta "
135 "name=\"yaze-collab-server\" content=\"wss://...\"> to enable.");
140 const std::string& session_name,
const std::string& username,
141 const std::string& password) {
142 if (is_connected_ || connection_state_ == ConnectionState::Connecting) {
143 return absl::FailedPreconditionError(
144 "Already connected or connecting to a session");
147 if (!IsConfigured()) {
148 return absl::FailedPreconditionError(
149 "Collaboration server not configured. Set "
150 "window.YAZE_CONFIG.collaborationServerUrl "
151 "or call SetWebSocketUrl() before creating a session.");
155 char* room_code_ptr = GenerateRandomRoomCode();
156 room_code_ = std::string(room_code_ptr);
159 session_name_ = session_name;
160 username_ = username;
161 stored_password_ = password;
162 should_reconnect_ =
true;
164 UpdateConnectionState(ConnectionState::Connecting,
"Creating session...");
167 auto status = websocket_->Connect(websocket_url_);
173 websocket_->OnOpen([
this, password]() {
174 ConsoleLog(
"WebSocket connected, creating session");
175 is_connected_ =
true;
176 UpdateConnectionState(ConnectionState::Connected,
"Connected");
180 self_user.id = user_id_;
181 self_user.name = username_;
182 self_user.color = user_color_;
183 self_user.is_active =
true;
184 self_user.last_activity = GetCurrentTime();
187 std::lock_guard<std::mutex> lock(users_mutex_);
188 users_[user_id_] = self_user;
193 msg[
"type"] =
"create";
194 msg[
"room"] = room_code_;
195 msg[
"name"] = session_name_;
196 msg[
"user"] = username_;
197 msg[
"user_id"] = user_id_;
198 msg[
"color"] = user_color_;
199 if (!password.empty()) {
200 msg[
"password"] = password;
203 auto send_status = websocket_->Send(msg.dump());
204 if (!send_status.ok()) {
205 ConsoleError(
"Failed to send create message");
208 if (status_callback_) {
209 status_callback_(
true,
"Session created");
211 UpdateCollaborationUI(
"session_created", room_code_.c_str());
214 websocket_->OnMessage(
215 [
this](
const std::string& message) { HandleMessage(message); });
217 websocket_->OnClose([
this](
int code,
const std::string& reason) {
218 is_connected_ =
false;
219 ConsoleLog(absl::StrFormat(
"WebSocket closed: %s (code: %d)", reason, code)
223 if (should_reconnect_) {
224 InitiateReconnection();
226 UpdateConnectionState(ConnectionState::Disconnected,
227 absl::StrFormat(
"Disconnected: %s", reason));
230 if (status_callback_) {
231 status_callback_(
false, absl::StrFormat(
"Disconnected: %s", reason));
233 UpdateCollaborationUI(
"disconnected",
"");
236 websocket_->OnError([
this](
const std::string& error) {
237 ConsoleError(error.c_str());
238 is_connected_ =
false;
241 if (should_reconnect_) {
242 InitiateReconnection();
244 UpdateConnectionState(ConnectionState::Disconnected, error);
247 if (status_callback_) {
248 status_callback_(
false, error);
256 UpdateCollaborationUI(
"session_creating", room_code_.c_str());
261 const std::string& username,
262 const std::string& password) {
263 if (is_connected_ || connection_state_ == ConnectionState::Connecting) {
264 return absl::FailedPreconditionError(
265 "Already connected or connecting to a session");
268 if (!IsConfigured()) {
269 return absl::FailedPreconditionError(
270 "Collaboration server not configured. Set "
271 "window.YAZE_CONFIG.collaborationServerUrl "
272 "or call SetWebSocketUrl() before joining a session.");
275 room_code_ = room_code;
276 username_ = username;
277 stored_password_ = password;
278 should_reconnect_ =
true;
280 UpdateConnectionState(ConnectionState::Connecting,
"Joining session...");
283 auto status = websocket_->Connect(websocket_url_);
289 websocket_->OnOpen([
this, password]() {
290 ConsoleLog(
"WebSocket connected, joining session");
291 is_connected_ =
true;
292 UpdateConnectionState(ConnectionState::Connected,
"Connected");
296 msg[
"type"] =
"join";
297 msg[
"room"] = room_code_;
298 msg[
"user"] = username_;
299 msg[
"user_id"] = user_id_;
300 msg[
"color"] = user_color_;
301 if (!password.empty()) {
302 msg[
"password"] = password;
305 auto send_status = websocket_->Send(msg.dump());
306 if (!send_status.ok()) {
307 ConsoleError(
"Failed to send join message");
310 if (status_callback_) {
311 status_callback_(
true,
"Joined session");
313 UpdateCollaborationUI(
"session_joined", room_code_.c_str());
316 websocket_->OnMessage(
317 [
this](
const std::string& message) { HandleMessage(message); });
319 websocket_->OnClose([
this](
int code,
const std::string& reason) {
320 is_connected_ =
false;
321 ConsoleLog(absl::StrFormat(
"WebSocket closed: %s (code: %d)", reason, code)
325 if (should_reconnect_) {
326 InitiateReconnection();
328 UpdateConnectionState(ConnectionState::Disconnected,
329 absl::StrFormat(
"Disconnected: %s", reason));
332 if (status_callback_) {
333 status_callback_(
false, absl::StrFormat(
"Disconnected: %s", reason));
335 UpdateCollaborationUI(
"disconnected",
"");
338 websocket_->OnError([
this](
const std::string& error) {
339 ConsoleError(error.c_str());
340 is_connected_ =
false;
343 if (should_reconnect_) {
344 InitiateReconnection();
346 UpdateConnectionState(ConnectionState::Disconnected, error);
349 if (status_callback_) {
350 status_callback_(
false, error);
355 UpdateCollaborationUI(
"session_joining", room_code_.c_str());
356 return absl::OkStatus();
360 if (!is_connected_ && connection_state_ != ConnectionState::Connecting &&
361 connection_state_ != ConnectionState::Reconnecting) {
362 return absl::FailedPreconditionError(
"Not connected to a session");
366 should_reconnect_ =
false;
371 msg[
"type"] =
"leave";
372 msg[
"room"] = room_code_;
373 msg[
"user_id"] = user_id_;
375 auto status = websocket_->Send(msg.dump());
377 ConsoleError(
"Failed to send leave message");
385 is_connected_ =
false;
386 UpdateConnectionState(ConnectionState::Disconnected,
"Left session");
390 session_name_.clear();
391 stored_password_.clear();
392 ResetReconnectionState();
395 std::lock_guard<std::mutex> lock(users_mutex_);
400 std::lock_guard<std::mutex> lock(cursors_mutex_);
404 if (status_callback_) {
405 status_callback_(
false,
"Left session");
408 UpdateCollaborationUI(
"session_left",
"");
409 return absl::OkStatus();
413 uint32_t offset,
const std::vector<uint8_t>& old_data,
414 const std::vector<uint8_t>& new_data) {
416 if (old_data.size() > max_size || new_data.size() > max_size) {
417 return absl::InvalidArgumentError(
418 absl::StrFormat(
"Change size exceeds maximum of %d bytes", max_size));
423 msg[
"type"] =
"change";
424 msg[
"room"] = room_code_;
425 msg[
"user_id"] = user_id_;
426 msg[
"offset"] = offset;
427 msg[
"old_data"] = old_data;
428 msg[
"new_data"] = new_data;
429 msg[
"timestamp"] = GetCurrentTime();
431 std::string message = msg.dump();
434 if (!is_connected_) {
435 if (connection_state_ == ConnectionState::Reconnecting) {
436 QueueMessageWhileDisconnected(message);
437 return absl::OkStatus();
439 return absl::FailedPreconditionError(
"Not connected to a session");
443 auto status = websocket_->Send(message);
446 if (connection_state_ == ConnectionState::Reconnecting) {
447 QueueMessageWhileDisconnected(message);
448 return absl::OkStatus();
450 return absl::InternalError(
"Failed to send change");
453 UpdateUserActivity(user_id_);
454 return absl::OkStatus();
457absl::Status WasmCollaboration::SendCursorPosition(
458 const std::string& editor_type,
int x,
int y,
int map_id) {
460 if (!is_connected_) {
461 if (connection_state_ == ConnectionState::Reconnecting) {
462 return absl::OkStatus();
464 return absl::FailedPreconditionError(
"Not connected to a session");
468 double now = GetCurrentTime();
469 double cursor_interval =
471 if (now - last_cursor_send_ < cursor_interval) {
472 return absl::OkStatus();
474 last_cursor_send_ = now;
478 msg[
"type"] =
"cursor";
479 msg[
"room"] = room_code_;
480 msg[
"user_id"] = user_id_;
481 msg[
"editor"] = editor_type;
485 msg[
"map_id"] = map_id;
488 auto status = websocket_->Send(msg.dump());
491 if (connection_state_ == ConnectionState::Reconnecting) {
492 return absl::OkStatus();
494 return absl::InternalError(
"Failed to send cursor position");
497 UpdateUserActivity(user_id_);
498 return absl::OkStatus();
503 std::lock_guard<std::mutex> lock(users_mutex_);
504 std::vector<User> result;
505 for (
const auto& [
id, user] : users_) {
506 if (user.is_active) {
507 result.push_back(user);
514 return is_connected_ && websocket_ && websocket_->IsConnected();
517void WasmCollaboration::ProcessPendingChanges() {
518 std::vector<ChangeEvent> changes_to_apply;
521 std::lock_guard<std::mutex> lock(changes_mutex_);
522 changes_to_apply = std::move(pending_changes_);
523 pending_changes_.clear();
526 for (
const auto& change : changes_to_apply) {
527 if (IsChangeValid(change)) {
528 ApplyRemoteChange(change);
536void WasmCollaboration::HandleMessage(
const std::string& message) {
538 json msg = json::parse(message);
539 std::string type = msg[
"type"];
541 if (type ==
"create_response") {
543 if (msg[
"success"]) {
544 session_name_ = msg[
"session_name"];
545 ConsoleLog(
"Session created successfully");
547 ConsoleError(msg[
"error"].get<std::string>().c_str());
548 is_connected_ =
false;
550 }
else if (type ==
"join_response") {
552 if (msg[
"success"]) {
553 session_name_ = msg[
"session_name"];
554 ConsoleLog(
"Joined session successfully");
556 ConsoleError(msg[
"error"].get<std::string>().c_str());
557 is_connected_ =
false;
559 }
else if (type ==
"users") {
561 std::lock_guard<std::mutex> lock(users_mutex_);
564 for (
const auto& user_data : msg[
"list"]) {
566 user.id = user_data[
"id"];
567 user.name = user_data[
"name"];
568 user.color = user_data[
"color"];
569 user.is_active = user_data[
"active"];
570 user.last_activity = GetCurrentTime();
571 users_[user.id] = user;
574 if (user_list_callback_) {
580 ui_data[
"users"] = msg[
"list"];
581 UpdateCollaborationUI(
"users_update", ui_data.dump().c_str());
583 }
else if (type ==
"change") {
585 if (msg[
"user_id"] != user_id_) {
587 change.offset = msg[
"offset"];
588 change.old_data = msg[
"old_data"].get<std::vector<uint8_t>>();
589 change.new_data = msg[
"new_data"].get<std::vector<uint8_t>>();
590 change.user_id = msg[
"user_id"];
591 change.timestamp = msg[
"timestamp"];
594 std::lock_guard<std::mutex> lock(changes_mutex_);
595 pending_changes_.push_back(change);
598 UpdateUserActivity(change.user_id);
600 }
else if (type ==
"cursor") {
602 if (msg[
"user_id"] != user_id_) {
604 cursor.user_id = msg[
"user_id"];
605 cursor.editor_type = msg[
"editor"];
608 if (msg.contains(
"map_id")) {
609 cursor.map_id = msg[
"map_id"];
613 std::lock_guard<std::mutex> lock(cursors_mutex_);
614 cursors_[cursor.user_id] = cursor;
617 if (cursor_callback_) {
618 cursor_callback_(cursor);
623 ui_data[
"user_id"] = cursor.user_id;
624 ui_data[
"editor"] = cursor.editor_type;
625 ui_data[
"x"] = cursor.x;
626 ui_data[
"y"] = cursor.y;
627 UpdateCollaborationUI(
"cursor_update", ui_data.dump().c_str());
629 UpdateUserActivity(cursor.user_id);
631 }
else if (type ==
"error") {
632 ConsoleError(msg[
"message"].get<std::string>().c_str());
633 if (status_callback_) {
634 status_callback_(
false, msg[
"message"]);
637 }
catch (
const json::exception& e) {
638 ConsoleError(absl::StrFormat(
"JSON parse error: %s", e.what()).c_str());
642std::string WasmCollaboration::GenerateUserId() {
643 std::random_device rd;
644 std::mt19937 gen(rd());
645 std::uniform_int_distribution<> dis(0, 15);
647 std::stringstream ss;
649 for (
int i = 0; i < 8; ++i) {
650 ss << std::hex << dis(gen);
655std::string WasmCollaboration::GenerateUserColor() {
656 std::random_device rd;
657 std::mt19937 gen(rd());
658 std::uniform_int_distribution<> dis(0, kUserColors.size() - 1);
659 return kUserColors[dis(gen)];
662void WasmCollaboration::UpdateUserActivity(
const std::string& user_id) {
663 std::lock_guard<std::mutex> lock(users_mutex_);
664 if (users_.find(user_id) != users_.end()) {
665 users_[user_id].last_activity = GetCurrentTime();
666 users_[user_id].is_active =
true;
670void WasmCollaboration::CheckUserTimeouts() {
671 double now = GetCurrentTime();
672 std::lock_guard<std::mutex> lock(users_mutex_);
675 bool users_changed =
false;
676 for (
auto& [
id, user] : users_) {
677 if (user.is_active && (now - user.last_activity) > timeout) {
678 user.is_active =
false;
679 users_changed =
true;
683 if (users_changed && user_list_callback_) {
688bool WasmCollaboration::IsChangeValid(
const ChangeEvent& change) {
694 if (change.offset + change.new_data.size() > rom_->size()) {
696 absl::StrFormat(
"Change at offset %u exceeds ROM size", change.offset)
705void WasmCollaboration::ApplyRemoteChange(
const ChangeEvent& change) {
707 ConsoleError(
"ROM not set, cannot apply changes");
711 applying_remote_change_ =
true;
713 for (
size_t i = 0; i < change.new_data.size(); ++i) {
714 rom_->WriteByte(change.offset + i, change.new_data[i]);
716 applying_remote_change_ =
false;
719 if (change_callback_) {
720 change_callback_(change);
725 ui_data[
"offset"] = change.offset;
726 ui_data[
"size"] = change.new_data.size();
727 ui_data[
"user_id"] = change.user_id;
728 UpdateCollaborationUI(
"change_applied", ui_data.dump().c_str());
731void WasmCollaboration::UpdateConnectionState(ConnectionState new_state,
732 const std::string& message) {
733 connection_state_ = new_state;
736 if (connection_state_callback_) {
737 connection_state_callback_(new_state, message);
741 std::string state_str;
743 case ConnectionState::Disconnected:
744 state_str =
"disconnected";
746 case ConnectionState::Connecting:
747 state_str =
"connecting";
749 case ConnectionState::Connected:
750 state_str =
"connected";
752 case ConnectionState::Reconnecting:
753 state_str =
"reconnecting";
758 ui_data[
"state"] = state_str;
759 ui_data[
"message"] = message;
760 UpdateCollaborationUI(
"connection_state", ui_data.dump().c_str());
763void WasmCollaboration::InitiateReconnection() {
764 if (!should_reconnect_ || room_code_.empty()) {
765 UpdateConnectionState(ConnectionState::Disconnected,
"Disconnected");
769 if (reconnection_attempts_ >= max_reconnection_attempts_) {
771 absl::StrFormat(
"Max reconnection attempts reached (%d), giving up",
772 max_reconnection_attempts_)
774 UpdateConnectionState(ConnectionState::Disconnected,
775 "Reconnection failed - max attempts reached");
776 ResetReconnectionState();
780 reconnection_attempts_++;
781 UpdateConnectionState(
782 ConnectionState::Reconnecting,
783 absl::StrFormat(
"Reconnecting... (attempt %d/%d)", reconnection_attempts_,
784 max_reconnection_attempts_));
787 double delay = std::min(
788 reconnection_delay_seconds_ * std::pow(2, reconnection_attempts_ - 1),
789 max_reconnection_delay_);
791 ConsoleLog(absl::StrFormat(
"Will reconnect in %.1f seconds (attempt %d)",
792 delay, reconnection_attempts_)
796 emscripten_async_call(
798 WasmCollaboration* self =
static_cast<WasmCollaboration*
>(arg);
799 self->AttemptReconnection();
804void WasmCollaboration::AttemptReconnection() {
805 if (is_connected_ || connection_state_ == ConnectionState::Connected) {
807 ResetReconnectionState();
811 ConsoleLog(absl::StrFormat(
"Attempting to reconnect to room %s", room_code_)
815 websocket_ = std::make_unique<net::EmscriptenWebSocket>();
818 auto status = websocket_->Connect(websocket_url_);
821 absl::StrFormat(
"Reconnection failed: %s", status.message()).c_str());
822 InitiateReconnection();
827 websocket_->OnOpen([
this]() {
828 ConsoleLog(
"WebSocket reconnected, rejoining session");
829 is_connected_ =
true;
830 UpdateConnectionState(ConnectionState::Connected,
831 "Reconnected successfully");
835 msg[
"type"] =
"join";
836 msg[
"room"] = room_code_;
837 msg[
"user"] = username_;
838 msg[
"user_id"] = user_id_;
839 msg[
"color"] = user_color_;
840 if (!stored_password_.empty()) {
841 msg[
"password"] = stored_password_;
843 msg[
"rejoin"] =
true;
845 auto send_status = websocket_->Send(msg.dump());
846 if (!send_status.ok()) {
847 ConsoleError(
"Failed to send rejoin message");
851 ResetReconnectionState();
854 std::vector<std::string> messages_to_send;
856 std::lock_guard<std::mutex> lock(message_queue_mutex_);
857 messages_to_send = std::move(queued_messages_);
858 queued_messages_.clear();
861 for (
const auto& msg : messages_to_send) {
862 websocket_->Send(msg);
865 if (status_callback_) {
866 status_callback_(
true,
"Reconnected to session");
868 UpdateCollaborationUI(
"session_reconnected", room_code_.c_str());
871 websocket_->OnMessage(
872 [
this](
const std::string& message) { HandleMessage(message); });
874 websocket_->OnClose([
this](
int code,
const std::string& reason) {
875 is_connected_ =
false;
877 absl::StrFormat(
"Reconnection WebSocket closed: %s", reason).c_str());
880 InitiateReconnection();
882 if (status_callback_) {
883 status_callback_(
false, absl::StrFormat(
"Disconnected: %s", reason));
887 websocket_->OnError([
this](
const std::string& error) {
888 ConsoleError(absl::StrFormat(
"Reconnection error: %s", error).c_str());
889 is_connected_ =
false;
892 InitiateReconnection();
894 if (status_callback_) {
895 status_callback_(
false, error);
900void WasmCollaboration::ResetReconnectionState() {
901 reconnection_attempts_ = 0;
902 reconnection_delay_seconds_ = 1.0;
905void WasmCollaboration::QueueMessageWhileDisconnected(
906 const std::string& message) {
907 std::lock_guard<std::mutex> lock(message_queue_mutex_);
910 if (queued_messages_.size() >= max_queued_messages_) {
911 ConsoleLog(
"Message queue full, dropping oldest message");
912 queued_messages_.erase(queued_messages_.begin());
915 queued_messages_.push_back(message);
916 ConsoleLog(absl::StrFormat(
"Queued message for reconnection (queue size: %d)",
917 queued_messages_.size())
926EMSCRIPTEN_KEEPALIVE
const char* WasmCollaborationCreate(
927 const char* session_name,
const char* username,
const char* password) {
928 static std::string last_room_code;
929 if (!session_name || !username) {
930 ConsoleError(
"Invalid session/user parameters");
933 auto& collab = GetInstance();
934 auto result = collab.CreateSession(session_name, username,
935 password ? std::string(password) :
"");
937 ConsoleError(std::string(result.status().message()).c_str());
940 last_room_code = *result;
941 return last_room_code.c_str();
944EMSCRIPTEN_KEEPALIVE
int WasmCollaborationJoin(
const char* room_code,
945 const char* username,
946 const char* password) {
947 if (!room_code || !username) {
948 ConsoleError(
"room_code and username are required");
951 auto& collab = GetInstance();
952 auto status = collab.JoinSession(room_code, username,
953 password ? std::string(password) :
"");
955 ConsoleError(std::string(status.message()).c_str());
961EMSCRIPTEN_KEEPALIVE
int WasmCollaborationLeave() {
962 auto& collab = GetInstance();
963 auto status = collab.LeaveSession();
964 return status.ok() ? 1 : 0;
967EMSCRIPTEN_KEEPALIVE
int WasmCollaborationSendCursor(
const char* editor_type,
968 int x,
int y,
int map_id) {
969 auto& collab = GetInstance();
970 auto status = collab.SendCursorPosition(editor_type ? editor_type :
"unknown",
972 return status.ok() ? 1 : 0;
975EMSCRIPTEN_KEEPALIVE
int WasmCollaborationBroadcastChange(
976 uint32_t offset,
const uint8_t* new_data,
size_t length) {
977 if (!new_data && length > 0) {
980 auto& collab = GetInstance();
981 std::vector<uint8_t> data;
982 data.reserve(length);
983 for (
size_t i = 0; i < length; ++i) {
984 data.push_back(new_data[i]);
986 std::vector<uint8_t> old_data;
987 auto status = collab.BroadcastChange(offset, old_data, data);
988 return status.ok() ? 1 : 0;
991EMSCRIPTEN_KEEPALIVE
void WasmCollaborationSetServerUrl(
const char* url) {
994 auto& collab = GetInstance();
995 collab.SetWebSocketUrl(std::string(url));
998EMSCRIPTEN_KEEPALIVE
int WasmCollaborationIsConnected() {
999 return GetInstance().IsConnected() ? 1 : 0;
1002EMSCRIPTEN_KEEPALIVE
const char* WasmCollaborationGetRoomCode() {
1003 static std::string room;
1004 room = GetInstance().GetRoomCode();
1005 return room.c_str();
1008EMSCRIPTEN_KEEPALIVE
const char* WasmCollaborationGetUserId() {
1009 static std::string user;
1010 user = GetInstance().GetUserId();
1011 return user.c_str();
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })