diff --git a/client/tp_assist_macos/TP-Assist.xcodeproj/project.pbxproj b/client/tp_assist_macos/TP-Assist.xcodeproj/project.pbxproj index ab8e3e8..e753890 100644 --- a/client/tp_assist_macos/TP-Assist.xcodeproj/project.pbxproj +++ b/client/tp_assist_macos/TP-Assist.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 7A18188F1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A18188E1F7D5D7F00F3C882 /* AppDelegate-C-Interface.cpp */; }; 7A1F87B1215D5A1600B69F88 /* StatusIconAlt@2X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */; }; 7A1F87B2215D5A1600B69F88 /* StatusIcon@2X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */; }; - 7A2034FB2833F355009E1491 /* tp-player.app in Resources */ = {isa = PBXBuildFile; fileRef = 7A2034FA2833F355009E1491 /* tp-player.app */; }; 7A27E4A91F6A8EEC004FDE5D /* ts_env.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */; }; 7A7C6C9121973C24006869D9 /* StatusIconAlt@3X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A7C6C8F21973C24006869D9 /* StatusIconAlt@3X.png */; }; 7A7C6C9221973C24006869D9 /* StatusIcon@3X.png in Resources */ = {isa = PBXBuildFile; fileRef = 7A7C6C9021973C24006869D9 /* StatusIcon@3X.png */; }; @@ -36,6 +35,7 @@ 7AAE4B242390EE5C007EDDE7 /* libmbedtls.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF9BF222199E32B00BE5DBC /* libmbedtls.a */; }; 7AAE4B252390EE7D007EDDE7 /* libmbedcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF9BF232199E32B00BE5DBC /* libmbedcrypto.a */; }; 7AAE4B262390EE7D007EDDE7 /* libmbedx509.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF9BF212199E32B00BE5DBC /* libmbedx509.a */; }; + 7AC630342835EB9A0080333D /* tp-player.app in Resources */ = {isa = PBXBuildFile; fileRef = 7AC630332835EB9A0080333D /* tp-player.app */; }; 7AF6593E27398C7400057A00 /* ts_ws_client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7AF6593C27398C7400057A00 /* ts_ws_client.cpp */; }; A1B7B9DD1DB53ED200809327 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A1B7B9DF1DB53ED200809327 /* Localizable.strings */; }; A1D700071A5DCE8D003563E4 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A1D700061A5DCE8D003563E4 /* AboutWindowController.m */; }; @@ -73,7 +73,6 @@ 7A1F87AA215D574500B69F88 /* version.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; name = version.h; path = ../../../../external/jsoncpp/include/json/version.h; sourceTree = ""; }; 7A1F87AF215D5A1600B69F88 /* StatusIconAlt@2X.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIconAlt@2X.png"; sourceTree = ""; }; 7A1F87B0215D5A1600B69F88 /* StatusIcon@2X.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "StatusIcon@2X.png"; sourceTree = ""; }; - 7A2034FA2833F355009E1491 /* tp-player.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "tp-player.app"; sourceTree = ""; }; 7A27E4A61F6A899B004FDE5D /* ts_const.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_const.h; sourceTree = ""; }; 7A27E4A71F6A8EEC004FDE5D /* ts_env.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ts_env.cpp; sourceTree = ""; }; 7A27E4A81F6A8EEC004FDE5D /* ts_env.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ts_env.h; sourceTree = ""; }; @@ -101,6 +100,7 @@ 7AA2CD501F6AB9F10074C92B /* json_writer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = json_writer.cpp; path = ../../../../external/jsoncpp/src/lib_json/json_writer.cpp; sourceTree = ""; }; 7AA2CD561F6ABA2E0074C92B /* mongoose.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mongoose.c; path = ../../../../external/mongoose/mongoose.c; sourceTree = ""; }; 7AAE4B232390E642007EDDE7 /* mongoose.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mongoose.h; path = ../../../../external/mongoose/mongoose.h; sourceTree = ""; }; + 7AC630332835EB9A0080333D /* tp-player.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; name = "tp-player.app"; path = "../../../out/client/x64/release/tp-player.app"; sourceTree = ""; }; 7AF6593C27398C7400057A00 /* ts_ws_client.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ts_ws_client.cpp; sourceTree = ""; }; 7AF6593D27398C7400057A00 /* ts_ws_client.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ts_ws_client.h; sourceTree = ""; }; 7AF9BF212199E32B00BE5DBC /* libmbedx509.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmbedx509.a; path = ../../external/macos/release/lib/libmbedx509.a; sourceTree = ""; }; @@ -314,7 +314,7 @@ C149EC0415D5214600B1F558 /* Supporting Files */ = { isa = PBXGroup; children = ( - 7A2034FA2833F355009E1491 /* tp-player.app */, + 7AC630332835EB9A0080333D /* tp-player.app */, C159DC2715D5DE7F00F5DE24 /* teleport.icns */, C149EC0515D5214600B1F558 /* TP-Assist-Info.plist */, C149EC0615D5214600B1F558 /* InfoPlist.strings */, @@ -396,7 +396,7 @@ 0ADB3B0D178EF8DB004E9BB9 /* StatusIcon.png in Resources */, 7A1F87B2215D5A1600B69F88 /* StatusIcon@2X.png in Resources */, 7A7C6C9221973C24006869D9 /* StatusIcon@3X.png in Resources */, - 7A2034FB2833F355009E1491 /* tp-player.app in Resources */, + 7AC630342835EB9A0080333D /* tp-player.app in Resources */, 0ADB3B0C178EF8DB004E9BB9 /* StatusIconAlt.png in Resources */, 7A1F87B1215D5A1600B69F88 /* StatusIconAlt@2X.png in Resources */, 7A7C6C9121973C24006869D9 /* StatusIconAlt@3X.png in Resources */, diff --git a/client/tp_assist_macos/src/AppDelegate-C-Interface.cpp b/client/tp_assist_macos/src/AppDelegate-C-Interface.cpp index ae7cc54..73f43a3 100644 --- a/client/tp_assist_macos/src/AppDelegate-C-Interface.cpp +++ b/client/tp_assist_macos/src/AppDelegate-C-Interface.cpp @@ -30,19 +30,7 @@ int cpp_main(void* _self, const char* bundle_path, const char* cfg_file, const c if(!g_cfg.init()) return -2; - TsWsClient::init_app(_self); + g_ws_client.init_app(_self); return 0; } - -//uint64_t cpp_getpid() -//{ -// pid_t pid = getpid(); -// -// return static_cast(pid); -//} - -void url_scheme_handler(const std::string& url) -{ - TsWsClient::url_scheme_handler(url); -} diff --git a/client/tp_assist_macos/src/AppDelegate-C-Interface.h b/client/tp_assist_macos/src/AppDelegate-C-Interface.h index 73e73f0..b35c055 100644 --- a/client/tp_assist_macos/src/AppDelegate-C-Interface.h +++ b/client/tp_assist_macos/src/AppDelegate-C-Interface.h @@ -18,9 +18,4 @@ int AppDelegate_select_app (void *_self); // for cpp global object initialize. int cpp_main(void* _self, const char* bundle_path, const char* cfg_file, const char* res_path, const char* log_path); -//unsigned long long cpp_getpid(); - -void url_scheme_handler(const std::string& url); - - #endif /* wrap_c_objc_h */ diff --git a/client/tp_assist_macos/src/AppDelegate.mm b/client/tp_assist_macos/src/AppDelegate.mm index f52fffc..8329561 100644 --- a/client/tp_assist_macos/src/AppDelegate.mm +++ b/client/tp_assist_macos/src/AppDelegate.mm @@ -54,12 +54,14 @@ int AppDelegate_select_app (void *_self) { // An error has occurred, do something to handle it NSLog(@"Failed to create directory \"%@\". Error: %@", logPath, error); - NSAlert *alert = [[NSAlert alloc] init]; - alert.icon = [NSImage imageNamed:@"tpassist"]; - [alert addButtonWithTitle:@"确定"]; - [alert setMessageText:@"无法启动Teleport助手"]; - [alert setInformativeText:@"无法创建目录 ~/tp-assist 来存储配置文件及日志文件。"]; - [alert runModal]; +// NSAlert *alert = [[NSAlert alloc] init]; +// alert.icon = [NSImage imageNamed:@"tpassist"]; +// [alert addButtonWithTitle:@"确定"]; +// [alert setMessageText:@"无法启动Teleport助手"]; +// [alert setInformativeText:@"无法创建目录 ~/tp-assist 来存储配置文件及日志文件。"]; +// [alert runModal]; + + [self my_alert:@"无法启动Teleport助手" msg:@"无法创建目录 ~/tp-assist 来存储配置文件及日志文件。"]; [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; [NSApp terminate:NSApp]; @@ -102,7 +104,7 @@ int AppDelegate_select_app (void *_self) { int ret = cpp_main((__bridge void*)self, bundle_path.c_str(), cpp_cfg_file.c_str(), cpp_res_path.c_str(), cpp_log_path.c_str()); if(ret != 0) { // http_rpc_stop(); - TsWsClient::stop_all_client(); + g_ws_client.stop_all_client(); NSString *msg = Nil; if(ret == -1) @@ -112,12 +114,13 @@ int AppDelegate_select_app (void *_self) { else msg = @"发生未知错误!"; - NSAlert *alert = [[NSAlert alloc] init]; - alert.icon = [NSImage imageNamed:@"tpassist"]; - [alert addButtonWithTitle:@"确定"]; - [alert setMessageText:@"无法启动Teleport助手"]; - [alert setInformativeText:msg]; - [alert runModal]; +// NSAlert *alert = [[NSAlert alloc] init]; +// alert.icon = [NSImage imageNamed:@"tpassist"]; +// [alert addButtonWithTitle:@"确定"]; +// [alert setMessageText:@"无法启动Teleport助手"]; +// [alert setInformativeText:msg]; +// [alert runModal]; + [self my_alert:@"无法启动Teleport助手" msg:msg]; [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; [NSApp terminate:NSApp]; @@ -232,13 +235,19 @@ int AppDelegate_select_app (void *_self) { } - (IBAction)quit:(id)sender { - TsWsClient::stop_all_client(); + g_ws_client.stop_all_client(); [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; [NSApp terminate:NSApp]; } -- (void)applicationDidFinishLaunching:(NSNotification *)notification +//- (void)applicationDidFinishLaunching:(NSNotification *)notification +//{ +// // once the program start, register URL scheme handler. +// [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; +//} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification { // once the program start, register URL scheme handler. [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; @@ -258,7 +267,7 @@ int AppDelegate_select_app (void *_self) { // // [self my_alert:@"URL Request" msg:url]; std::string _url = [url cStringUsingEncoding:NSUTF8StringEncoding]; - url_scheme_handler(_url); + g_ws_client.url_scheme_handler(_url); } - (void)my_alert:(NSString*) title msg:(NSString*)message { diff --git a/client/tp_assist_macos/src/csrc/ts_env.cpp b/client/tp_assist_macos/src/csrc/ts_env.cpp index 7211800..e1cc043 100644 --- a/client/tp_assist_macos/src/csrc/ts_env.cpp +++ b/client/tp_assist_macos/src/csrc/ts_env.cpp @@ -27,7 +27,7 @@ bool TsEnv::init(const char* bundle_path, const char* cfg_file, const char* res_ #ifdef EX_DEBUG //m_site_path = L"/Users/apex/work/tp4a/teleport/client/tp_assist_macos/site"; - m_bundle_path = L"/Users/apex/work/tp4a/teleport/out/client/x64/release"; + //m_bundle_path = L"/Users/apex/work/tp4a/teleport/out/client/x64/release"; #else //m_site_path = m_res_path; //ex_path_join(m_site_path, false, L"site", NULL); diff --git a/client/tp_assist_macos/src/csrc/ts_ws_client.cpp b/client/tp_assist_macos/src/csrc/ts_ws_client.cpp index 5799674..a536c2d 100644 --- a/client/tp_assist_macos/src/csrc/ts_ws_client.cpp +++ b/client/tp_assist_macos/src/csrc/ts_ws_client.cpp @@ -14,11 +14,9 @@ #include "ts_cfg.h" TsWsClient g_ws_client; -TsWsClient g_wss_client; void* g_app = NULL; -// static void TsWsClient::init_app(void* app) { g_app = app; @@ -27,15 +25,17 @@ void TsWsClient::init_app(void* app) void TsWsClient::stop_all_client() { g_ws_client.stop(); - g_wss_client.stop(); } // ============================================================================ // static void TsWsClient::url_scheme_handler(const std::string& url) { + // e.g.: // url: teleport://register?param={"ws_url":"ws://127.0.0.1:7190/ws/assist/","assist_id":1234,"session_id":"tp_5678"} + EXLOGV("url-schema: %s\n", url.c_str()); + std::string protocol; std::string method; std::string param; @@ -61,11 +61,10 @@ void TsWsClient::url_scheme_handler(const std::string& url) return; } - // now we support 'register' method only. method.assign(url, pos_protocol + 3, pos_method - pos_protocol - 3); - if (method != "register") + if(method.empty()) { - EXLOGE("[ws] unknown method: %s\n", method.c_str()); + EXLOGE("[ws] no method, what should I do now?\n"); return; } @@ -108,11 +107,25 @@ void TsWsClient::url_scheme_handler(const std::string& url) return; } - // now we support 'register' method only. - _process_register(param, js_root); + if (method == "register") + { + _process_register(param, js_root); + } + else if(method == "run") + { + _process_run(param, js_root); + } + else if(method == "replay_rdp") + { + _process_replay_rdp(param, js_root); + } + else + { + EXLOGE("[ws] unknown method: %s\n", method.c_str()); + return; + } } -// static void TsWsClient::_process_register(const std::string& param, Json::Value& js_root) { // {"ws_url":"ws://127.0.0.1:7190/ws/assist/","assist_id":1234,"session_id":"tp_5678"} @@ -129,15 +142,11 @@ void TsWsClient::_process_register(const std::string& param, Json::Value& js_roo std::string session_id = js_root["session_id"].asCString(); std::string protocol; - protocol.assign(ws_url, 0, 3); - if (protocol == "ws:") + protocol.assign(ws_url, 0, 5); + if (protocol == "ws://" || protocol == "wss:/") { g_ws_client._register(ws_url, assist_id, session_id); } - else if (protocol == "wss") - { - g_wss_client._register(ws_url, assist_id, session_id); - } else { EXLOGE("[ws] invalid ws_url: %s\n", ws_url.c_str()); @@ -145,6 +154,31 @@ void TsWsClient::_process_register(const std::string& param, Json::Value& js_roo } } +void TsWsClient::_process_run(const std::string& param, Json::Value& js_root) +{ + // wrapper for _rpc_func_run_client(). + + Json::Value js_param; + js_param["method"] = "run"; + js_param["param"] = js_root; + + AssistMessage msg_req; + std::string buf; + _rpc_func_run_client(buf, msg_req, js_param); +} + +void TsWsClient::_process_replay_rdp(const std::string& param, Json::Value& js_root) +{ + // wrapper for _rpc_func_replay_rdp(). + + Json::Value js_param; + js_param["method"] = "replay_rdp"; + js_param["param"] = js_root; + + AssistMessage msg_req; + std::string buf; + _rpc_func_replay_rdp(buf, msg_req, js_param); +} // ============================================================================ @@ -466,6 +500,7 @@ void TsWsClient::_rpc_func_replay_rdp(ex_astr& buf, AssistMessage& msg_req, Json ex_wstr w_exec_file = g_env.m_bundle_path; ex_path_join(w_exec_file, false, L"Contents", L"Resources", L"tp-player.app", L"Contents", L"MacOS", L"tp-player", nullptr); + // ex_path_join(w_exec_file, false, L"tp-player.app", L"Contents", L"MacOS", L"tp-player", nullptr); ex_astr exec_file; ex_wstr2astr(w_exec_file, exec_file); diff --git a/client/tp_assist_macos/src/csrc/ts_ws_client.h b/client/tp_assist_macos/src/csrc/ts_ws_client.h index c135471..1a54ee9 100644 --- a/client/tp_assist_macos/src/csrc/ts_ws_client.h +++ b/client/tp_assist_macos/src/csrc/ts_ws_client.h @@ -33,10 +33,10 @@ public: ~TsWsClient(); - static void init_app(void* app); - static void stop_all_client(); + void init_app(void* app); + void stop_all_client(); - static void url_scheme_handler(const std::string& url); + void url_scheme_handler(const std::string& url); protected: void _thread_loop(void); @@ -60,23 +60,18 @@ private: void _send_result(int err_code, Json::Value& jr_root); - void _create_response(ex_astr& buf, const AssistMessage& msg_ret, int err_code); void _create_response(ex_astr& buf, const AssistMessage& msg_ret, int err_code, const std::string& message); void _create_response(ex_astr& buf, const AssistMessage& msg_ret, int err_code, const std::string& message, Json::Value& data); - // void _rpc_func_check(const ex_astr& func_args, ex_astr& buf); - // void _rpc_func_rdp_play(const ex_astr& func_args, ex_astr& buf); - // void _rpc_func_file_action(const ex_astr& func_args, ex_astr& buf); - // void _rpc_func_get_version(const ex_astr& func_args, ex_astr& buf); - - - static void _mg_event_handler(struct mg_connection* nc, int ev, void* ev_data); - static void _process_register(const std::string& param, Json::Value& js_root); + void _process_register(const std::string& param, Json::Value& js_root); + + void _process_run(const std::string& param, Json::Value& js_root); + void _process_replay_rdp(const std::string& param, Json::Value& js_root); private: struct mg_mgr m_mg_mgr; @@ -84,4 +79,6 @@ private: uint32_t m_assist_id; }; +extern TsWsClient g_ws_client; + #endif // __TS_WS_CLIENT_H__ diff --git a/server/www/teleport/static/js/teleport.js b/server/www/teleport/static/js/teleport.js index 3b9f7b0..9c0e8a4 100644 --- a/server/www/teleport/static/js/teleport.js +++ b/server/www/teleport/static/js/teleport.js @@ -64,7 +64,7 @@ let $tp = { options: {} , on_init: function (cb_stack) { cb_stack.exec(); - } // should be overwrite. + } // should be overwritten. } , assist_checked: null diff --git a/server/www/teleport/static/js/tp-assist.js b/server/www/teleport/static/js/tp-assist.js index 29488a6..9a32926 100644 --- a/server/www/teleport/static/js/tp-assist.js +++ b/server/www/teleport/static/js/tp-assist.js @@ -235,7 +235,7 @@ $assist._on_ret_run = function (code, message, ret) { return; } - $tp.notify_success('已启动本地客户端进行远程连接!'); + $tp.notify_success('已通知助手启动本地客户端进行远程连接!'); } $assist._on_ret_replay_rdp = function (code, message, ret) { @@ -247,7 +247,7 @@ $assist._on_ret_replay_rdp = function (code, message, ret) { return; } - $tp.notify_success('已启动本地RDP录像播放器!'); + $tp.notify_success('已通知助手启动本地RDP录像播放器!'); } $assist.check_assist = function () { diff --git a/server/www/teleport/webroot/app/base/controller.py b/server/www/teleport/webroot/app/base/controller.py index 7c71773..7f85471 100644 --- a/server/www/teleport/webroot/app/base/controller.py +++ b/server/www/teleport/webroot/app/base/controller.py @@ -101,6 +101,10 @@ class TPBaseHandler(tornado.web.RequestHandler): # x = self.xsrf_token self._s_id = self.get_cookie('_sid') + if self._s_id is None: + print(self.request.headers) + if 'Teleport-Session-Id' in self.request.headers: + self._s_id = self.request.headers['Teleport-Session-Id'] if self._s_id is None: self._s_id = 'tp_{}_{}'.format(int(time.time()), binascii.b2a_hex(os.urandom(8)).decode()) self.set_cookie('_sid', self._s_id) diff --git a/server/www/teleport/webroot/app/controller/__init__.py b/server/www/teleport/webroot/app/controller/__init__.py index c8b49e6..ab29515 100755 --- a/server/www/teleport/webroot/app/controller/__init__.py +++ b/server/www/teleport/webroot/app/controller/__init__.py @@ -302,6 +302,7 @@ controllers = [ # api v2 (r'/api/v2/request_session', api_v2.RequestSessionHandler), + (r'/api/v2/request_access_token', api_v2.RequestAccessTokenHandler), (r'/.*', index.CatchAllHandler), ] diff --git a/server/www/teleport/webroot/app/controller/api_v2.py b/server/www/teleport/webroot/app/controller/api_v2.py index b9d445c..70df3ed 100644 --- a/server/www/teleport/webroot/app/controller/api_v2.py +++ b/server/www/teleport/webroot/app/controller/api_v2.py @@ -93,6 +93,7 @@ def _parse_api_args(handler): return False, handler.write_json(TPE_JSON_FORMAT) args['_srv_name_'] = sec_info['name'] + args['_privilege'] = sec_info['privilege'] return True, args @@ -128,3 +129,34 @@ class RequestSessionHandler(TPBaseJsonHandler): return self.write_json(ret['code'], ret['message']) return self.write_json(TPE_OK, data=ret['data']) + + +class RequestAccessTokenHandler(TPBaseJsonHandler): + + @tornado.gen.coroutine + def post(self): + ok, args = yield _parse_api_args(self) + if not ok: + return + + try: + operator = args['operator'] + privilege = args['_privilege'] + except: + return self.write_json(TPE_PARAM) + + operator_surname = '[{}] {}'.format(args['_srv_name_'], operator) + + _user = { + 'id': 0, + 'username': operator, + 'surname': operator_surname, + 'role_id': 0, + 'role': '', + 'privilege': privilege, + '_is_login': True + } + self.set_session('user', _user, 60) + + self._user = _user + return self.write_json(TPE_OK, data={'sid': self._s_id})