@memtensor/memos-local-openclaw-plugin 1.0.6-beta.1 → 1.0.6-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +10 -4
- package/dist/client/connector.js.map +1 -1
- package/dist/hub/server.d.ts +2 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +108 -54
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +4 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +20 -4
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +0 -3
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +9 -8
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +35 -43
- package/dist/recall/engine.js.map +1 -1
- package/dist/storage/sqlite.d.ts +13 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +43 -1
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +71 -21
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +86 -34
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +21 -5
- package/package.json +1 -1
- package/src/client/connector.ts +10 -4
- package/src/hub/server.ts +95 -51
- package/src/ingest/providers/index.ts +26 -18
- package/src/ingest/providers/openai.ts +5 -4
- package/src/recall/engine.ts +34 -41
- package/src/storage/sqlite.ts +43 -1
- package/src/viewer/html.ts +71 -21
- package/src/viewer/server.ts +91 -31
package/src/viewer/html.ts
CHANGED
|
@@ -6,7 +6,7 @@ return `<!DOCTYPE html>
|
|
|
6
6
|
<meta charset="UTF-8">
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
8
|
<link rel="icon" href="https://statics.memtensor.com.cn/logo/color-m.svg" type="image/svg+xml">
|
|
9
|
-
<title>
|
|
9
|
+
<title>OpenClaw 记忆</title>
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -1192,7 +1192,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1192
1192
|
<div class="topbar-inner">
|
|
1193
1193
|
<div class="brand">
|
|
1194
1194
|
<span class="memos-logo"><svg width="28" height="28" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="topLG" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#ff4d4d"/><stop offset="100%" stop-color="#991b1b"/></linearGradient></defs><path d="M60 10C30 10 15 35 15 55C15 75 30 95 45 100L45 110L55 110L55 100C55 100 60 102 65 100L65 110L75 110L75 100C90 95 105 75 105 55C105 35 90 10 60 10Z" fill="url(#topLG)"/><path d="M20 45C5 40 0 50 5 60C10 70 20 65 25 55C28 48 25 45 20 45Z" fill="url(#topLG)"/><path d="M100 45C115 40 120 50 115 60C110 70 100 65 95 55C92 48 95 45 100 45Z" fill="url(#topLG)"/><path d="M45 15Q35 5 30 8" stroke="#ff4d4d" stroke-width="2" stroke-linecap="round"/><path d="M75 15Q85 5 90 8" stroke="#ff4d4d" stroke-width="2" stroke-linecap="round"/><circle cx="45" cy="35" r="6" fill="#050810"/><circle cx="75" cy="35" r="6" fill="#050810"/><circle cx="46" cy="34" r="2" fill="#00e5cc"/><circle cx="76" cy="34" r="2" fill="#00e5cc"/></svg></span>
|
|
1195
|
-
<div class="brand-col"><span data-i18n="title" class="brand-title">
|
|
1195
|
+
<div class="brand-col"><span data-i18n="title" class="brand-title">OpenClaw 记忆</span><span data-i18n="subtitle" class="brand-powered">Powered by MemOS</span></div>${vBadge}
|
|
1196
1196
|
</div>
|
|
1197
1197
|
<div class="topbar-center">
|
|
1198
1198
|
<nav class="nav-tabs">
|
|
@@ -2038,7 +2038,7 @@ let _currentAgentOwner='agent:main';
|
|
|
2038
2038
|
/* ─── i18n ─── */
|
|
2039
2039
|
const I18N={
|
|
2040
2040
|
en:{
|
|
2041
|
-
'title':'
|
|
2041
|
+
'title':'OpenClaw Memory',
|
|
2042
2042
|
'subtitle':'Powered by MemOS',
|
|
2043
2043
|
'setup.desc':'Set a password to protect your memories',
|
|
2044
2044
|
'setup.pw':'Enter a password (4+ characters)',
|
|
@@ -2130,6 +2130,8 @@ const I18N={
|
|
|
2130
2130
|
'notif.hubShutdown':'The team server has been shut down',
|
|
2131
2131
|
'notif.rolePromoted':'You have been promoted to admin',
|
|
2132
2132
|
'notif.roleDemoted':'You have been changed to member',
|
|
2133
|
+
'notif.usernameRenamed':'Your nickname has been changed by the admin',
|
|
2134
|
+
'notif.usernameRenamed.detail':'{oldName} → {newName}',
|
|
2133
2135
|
'notif.clearAll':'Clear all',
|
|
2134
2136
|
'notif.timeAgo.just':'just now',
|
|
2135
2137
|
'notif.timeAgo.min':'{n}m ago',
|
|
@@ -2469,6 +2471,10 @@ const I18N={
|
|
|
2469
2471
|
'sharing.team.default':'the team',
|
|
2470
2472
|
'sharing.retryJoin.success':'Join request re-submitted. Waiting for admin approval.',
|
|
2471
2473
|
'sharing.retryJoin.fail':'Failed to retry join',
|
|
2474
|
+
'sharing.joinError.hubUnreachable':'Unable to connect to the team server. The server may be offline or the network is unavailable. Please check the server address and try again.',
|
|
2475
|
+
'sharing.joinError.usernameTaken':'The username is already taken on this team server. Please go to Settings and change your nickname, then try again.',
|
|
2476
|
+
'sharing.joinError.invalidToken':'The team token is invalid. Please check with your team admin and update the token in Settings.',
|
|
2477
|
+
'sharing.joinError.blocked':'Your account has been blocked by the team admin. Please contact the admin for assistance.',
|
|
2472
2478
|
'sharing.ownerRemoved':'(removed)',
|
|
2473
2479
|
'sharing.cannotJoinSelf':'Cannot join your own server. Please enter a remote server address.',
|
|
2474
2480
|
'scope.hub':'Team',
|
|
@@ -2775,7 +2781,7 @@ const I18N={
|
|
|
2775
2781
|
'guide.hub.btn':'\u2192 Configure Server Mode'
|
|
2776
2782
|
},
|
|
2777
2783
|
zh:{
|
|
2778
|
-
'title':'
|
|
2784
|
+
'title':'OpenClaw 记忆',
|
|
2779
2785
|
'subtitle':'由 MemOS 驱动',
|
|
2780
2786
|
'setup.desc':'设置密码以保护你的记忆数据',
|
|
2781
2787
|
'setup.pw':'输入密码(至少4位)',
|
|
@@ -2867,6 +2873,8 @@ const I18N={
|
|
|
2867
2873
|
'notif.hubShutdown':'团队服务已关闭',
|
|
2868
2874
|
'notif.rolePromoted':'你已被提升为管理员',
|
|
2869
2875
|
'notif.roleDemoted':'你已被设为普通成员',
|
|
2876
|
+
'notif.usernameRenamed':'你的昵称已被管理员修改',
|
|
2877
|
+
'notif.usernameRenamed.detail':'{oldName} → {newName}',
|
|
2870
2878
|
'notif.clearAll':'清除全部',
|
|
2871
2879
|
'notif.timeAgo.just':'刚刚',
|
|
2872
2880
|
'notif.timeAgo.min':'{n}分钟前',
|
|
@@ -3206,6 +3214,10 @@ const I18N={
|
|
|
3206
3214
|
'sharing.team.default':'该团队',
|
|
3207
3215
|
'sharing.retryJoin.success':'加入申请已重新提交,请等待管理员审核。',
|
|
3208
3216
|
'sharing.retryJoin.fail':'重新申请失败',
|
|
3217
|
+
'sharing.joinError.hubUnreachable':'无法连接到团队服务器,服务器可能已下线或网络不可用。请检查服务器地址后重试。',
|
|
3218
|
+
'sharing.joinError.usernameTaken':'该用户名在团队服务器上已被占用,请到设置中修改昵称后再重试。',
|
|
3219
|
+
'sharing.joinError.invalidToken':'团队令牌无效,请向管理员确认令牌并在设置中更新。',
|
|
3220
|
+
'sharing.joinError.blocked':'您的账号已被团队管理员封禁,请联系管理员处理。',
|
|
3209
3221
|
'sharing.ownerRemoved':'(已移除)',
|
|
3210
3222
|
'sharing.cannotJoinSelf':'不能加入自己的服务端,请输入远程服务器地址。',
|
|
3211
3223
|
'scope.hub':'团队',
|
|
@@ -3529,7 +3541,7 @@ function applyI18n(){
|
|
|
3529
3541
|
});
|
|
3530
3542
|
const step2=document.getElementById('resetStep2Desc');
|
|
3531
3543
|
if(step2) step2.innerHTML=t('reset.step2.desc.pre')+'<span style="font-family:monospace;font-size:12px;color:var(--pri)">password reset token: <strong>a1b2c3d4e5f6...</strong></span>'+t('reset.step2.desc.post');
|
|
3532
|
-
document.title=t('title')+' -
|
|
3544
|
+
document.title=t('title')+' - OpenClaw';
|
|
3533
3545
|
if(typeof loadStats==='function' && document.getElementById('app').style.display==='flex'){loadStats();}
|
|
3534
3546
|
if(document.querySelector('.analytics-view.show') && typeof loadMetrics==='function'){loadMetrics();}
|
|
3535
3547
|
}
|
|
@@ -3702,18 +3714,25 @@ async function testHubConnection(){
|
|
|
3702
3714
|
var result=document.getElementById('hubConnTestResult');
|
|
3703
3715
|
var addr=document.getElementById('cfgClientHubAddress').value.trim();
|
|
3704
3716
|
if(!addr){result.innerHTML='<span style="color:var(--rose)">\u274C '+t('settings.hub.test.noAddr')+'</span>';return;}
|
|
3717
|
+
var tokenEl=document.getElementById('cfgClientTeamToken');
|
|
3718
|
+
var teamToken=tokenEl?tokenEl.value.trim():'';
|
|
3719
|
+
if(!teamToken){result.innerHTML='<span style="color:var(--rose)">\u274C '+t('settings.hub.teamToken.required')+'</span>';return;}
|
|
3720
|
+
var nicknameEl=document.getElementById('cfgClientNickname');
|
|
3721
|
+
var nickname=nicknameEl?nicknameEl.value.trim():'';
|
|
3705
3722
|
btn.disabled=true;result.innerHTML=t('settings.hub.test.testing');
|
|
3706
|
-
try{
|
|
3707
|
-
}catch(e){}
|
|
3708
3723
|
try{
|
|
3709
3724
|
var url=addr.match(/^https?:\\/\\//)?addr:'http://'+addr;
|
|
3710
3725
|
url=url.replace(/\\/+$/,'');
|
|
3711
|
-
var r=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:url})});
|
|
3726
|
+
var r=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:url,teamToken:teamToken,nickname:nickname})});
|
|
3712
3727
|
var d=await r.json();
|
|
3713
3728
|
if(d.ok){
|
|
3714
3729
|
result.innerHTML='<span style="color:var(--green)">\u2705 '+t('settings.hub.test.ok')+(d.teamName?' — '+esc(d.teamName):'')+'</span>';
|
|
3715
3730
|
}else{
|
|
3716
|
-
var errMsg
|
|
3731
|
+
var errMsg;
|
|
3732
|
+
if(d.error==='cannot_join_self') errMsg=t('sharing.cannotJoinSelf');
|
|
3733
|
+
else if(d.error==='username_taken') errMsg=t('sharing.joinError.usernameTaken');
|
|
3734
|
+
else if(d.error==='invalid_team_token') errMsg=t('sharing.joinError.invalidToken');
|
|
3735
|
+
else errMsg=d.error||t('settings.hub.test.fail');
|
|
3717
3736
|
result.innerHTML='<span style="color:var(--rose)">\u274C '+errMsg+'</span>';
|
|
3718
3737
|
}
|
|
3719
3738
|
}catch(e){
|
|
@@ -4079,7 +4098,18 @@ async function retryHubJoin(){
|
|
|
4079
4098
|
_lastSidebarFingerprint='';_lastSettingsFingerprint='';_lastSharingConnStatus='';
|
|
4080
4099
|
setTimeout(function(){loadSharingStatus(true);},800);
|
|
4081
4100
|
}else{
|
|
4082
|
-
|
|
4101
|
+
var code=d.errorCode||'';
|
|
4102
|
+
if(code==='hub_unreachable'){
|
|
4103
|
+
alertModal(t('sharing.joinError.hubUnreachable'));
|
|
4104
|
+
}else if(code==='username_taken'){
|
|
4105
|
+
alertModal(t('sharing.joinError.usernameTaken'));
|
|
4106
|
+
}else if(code==='invalid_team_token'){
|
|
4107
|
+
alertModal(t('sharing.joinError.invalidToken'));
|
|
4108
|
+
}else if(code==='blocked'){
|
|
4109
|
+
alertModal(t('sharing.joinError.blocked'));
|
|
4110
|
+
}else{
|
|
4111
|
+
toast(d.error||t('sharing.retryJoin.fail'),'error');
|
|
4112
|
+
}
|
|
4083
4113
|
}
|
|
4084
4114
|
}catch(e){toast(t('sharing.retryJoin.fail')+': '+e.message,'error');}
|
|
4085
4115
|
}
|
|
@@ -6666,7 +6696,8 @@ async function loadConfig(){
|
|
|
6666
6696
|
document.getElementById('cfgClientHubAddress').value=client.hubAddress||'';
|
|
6667
6697
|
_loadedClientHubAddress=client.hubAddress||'';
|
|
6668
6698
|
document.getElementById('cfgClientTeamToken').value=client.teamToken||'';
|
|
6669
|
-
|
|
6699
|
+
var hubUsername=sharingStatusCache&&sharingStatusCache.connection&&sharingStatusCache.connection.user&&sharingStatusCache.connection.user.username;
|
|
6700
|
+
document.getElementById('cfgClientNickname').value=hubUsername||client.nickname||'';
|
|
6670
6701
|
document.getElementById('cfgClientUserToken').value=client.userToken||'';
|
|
6671
6702
|
onSharingToggle();
|
|
6672
6703
|
updateHubShareInfo();
|
|
@@ -6867,19 +6898,19 @@ async function saveHubConfig(){
|
|
|
6867
6898
|
if(clientUserToken) cfg.sharing.client.userToken=clientUserToken;
|
|
6868
6899
|
cfg.sharing.hub={teamName:'',teamToken:''};
|
|
6869
6900
|
if(clientAddr){
|
|
6870
|
-
try{
|
|
6871
|
-
}catch(e){}
|
|
6872
6901
|
try{
|
|
6873
6902
|
var testUrl=clientAddr.indexOf('://')>-1?clientAddr:'http://'+clientAddr;
|
|
6874
6903
|
testUrl=testUrl.replace(/\\/+$/,'');
|
|
6875
|
-
var tr=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:testUrl})});
|
|
6904
|
+
var tr=await fetch('/api/sharing/test-hub',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({hubUrl:testUrl,teamToken:clientTeamToken,nickname:clientNickname})});
|
|
6876
6905
|
var td=await tr.json();
|
|
6877
6906
|
if(!td.ok){
|
|
6878
|
-
|
|
6879
|
-
done();
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6907
|
+
if(td.error==='cannot_join_self'){done();alertModal(t('sharing.cannotJoinSelf'));return;}
|
|
6908
|
+
if(td.error==='username_taken'){done();alertModal(t('sharing.joinError.usernameTaken'));return;}
|
|
6909
|
+
if(td.error==='invalid_team_token'){done();alertModal(t('sharing.joinError.invalidToken'));return;}
|
|
6910
|
+
done();alertModal(td.error||t('settings.hub.test.fail'));return;
|
|
6911
|
+
}
|
|
6912
|
+
}catch(e){
|
|
6913
|
+
done();alertModal(t('sharing.joinError.hubUnreachable'));return;
|
|
6883
6914
|
}
|
|
6884
6915
|
}
|
|
6885
6916
|
}
|
|
@@ -6909,7 +6940,17 @@ async function saveHubConfig(){
|
|
|
6909
6940
|
try{await fetch('/api/sharing/update-username',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:adminNameEl.value.trim()})});}catch(e){}
|
|
6910
6941
|
}
|
|
6911
6942
|
}
|
|
6912
|
-
if(sharingEnabled&&_sharingRole==='client'&&result.
|
|
6943
|
+
if(sharingEnabled&&_sharingRole==='client'&&result.joinError){
|
|
6944
|
+
if(result.joinError==='hub_unreachable'){
|
|
6945
|
+
alertModal(t('sharing.joinError.hubUnreachable'));
|
|
6946
|
+
}else if(result.joinError==='username_taken'){
|
|
6947
|
+
alertModal(t('sharing.joinError.usernameTaken'));
|
|
6948
|
+
}else if(result.joinError==='invalid_team_token'){
|
|
6949
|
+
alertModal(t('sharing.joinError.invalidToken'));
|
|
6950
|
+
}else{
|
|
6951
|
+
toast(t('sharing.retryJoin.fail'),'error');
|
|
6952
|
+
}
|
|
6953
|
+
}else if(sharingEnabled&&_sharingRole==='client'&&result.joinStatus){
|
|
6913
6954
|
if(result.joinStatus==='pending'){
|
|
6914
6955
|
toast(t('sharing.joinSent.pending'),'success');
|
|
6915
6956
|
}else if(result.joinStatus==='active'){
|
|
@@ -7477,6 +7518,7 @@ function notifIcon(resource,type){
|
|
|
7477
7518
|
if(type==='hub_shutdown') return '\\u{1F6D1}';
|
|
7478
7519
|
if(type==='role_promoted') return '\\u{2B06}';
|
|
7479
7520
|
if(type==='role_demoted') return '\\u{2B07}';
|
|
7521
|
+
if(type==='username_renamed') return '\\u{270F}';
|
|
7480
7522
|
if(resource==='memory') return '\\u{1F4DD}';
|
|
7481
7523
|
if(resource==='task') return '\\u{1F4CB}';
|
|
7482
7524
|
if(resource==='skill') return '\\u{1F9E0}';
|
|
@@ -7523,6 +7565,9 @@ function notifTypeText(n){
|
|
|
7523
7565
|
if(n.type==='role_demoted'){
|
|
7524
7566
|
return t('notif.roleDemoted');
|
|
7525
7567
|
}
|
|
7568
|
+
if(n.type==='username_renamed'){
|
|
7569
|
+
return t('notif.usernameRenamed');
|
|
7570
|
+
}
|
|
7526
7571
|
return n.message||n.type;
|
|
7527
7572
|
}
|
|
7528
7573
|
|
|
@@ -7557,7 +7602,7 @@ function renderNotifBadge(){
|
|
|
7557
7602
|
}
|
|
7558
7603
|
}
|
|
7559
7604
|
|
|
7560
|
-
var _notifKnownTypes={membership_approved:1,membership_rejected:1,membership_removed:1,hub_shutdown:1,user_left:1,user_online:1,user_offline:1,user_join_request:1,role_promoted:1,role_demoted:1,resource_removed:1,resource_shared:1,resource_unshared:1};
|
|
7605
|
+
var _notifKnownTypes={membership_approved:1,membership_rejected:1,membership_removed:1,hub_shutdown:1,user_left:1,user_online:1,user_offline:1,user_join_request:1,role_promoted:1,role_demoted:1,resource_removed:1,resource_shared:1,resource_unshared:1,username_renamed:1};
|
|
7561
7606
|
function notifDisplayTitle(n){
|
|
7562
7607
|
if(_notifKnownTypes[n.type]) return notifTypeText(n);
|
|
7563
7608
|
return n.title||notifTypeText(n);
|
|
@@ -7565,6 +7610,11 @@ function notifDisplayTitle(n){
|
|
|
7565
7610
|
function notifDisplayDetail(n){
|
|
7566
7611
|
if(_notifKnownTypes[n.type]){
|
|
7567
7612
|
if(n.type==='resource_removed'||n.type==='resource_shared'||n.type==='resource_unshared') return n.title||'';
|
|
7613
|
+
if(n.type==='username_renamed'){
|
|
7614
|
+
var rm=n.title&&n.title.match(/from "([^"]+)" to "([^"]+)"/);
|
|
7615
|
+
if(rm) return t('notif.usernameRenamed.detail').replace('{oldName}',rm[1]).replace('{newName}',rm[2]);
|
|
7616
|
+
return '';
|
|
7617
|
+
}
|
|
7568
7618
|
var m=n.title&&n.title.match(/["\u201C]([^"\u201D]+)["\u201D]/);
|
|
7569
7619
|
if(m) return m[1];
|
|
7570
7620
|
if(n.type==='user_left'||n.type==='user_online'||n.type==='user_offline'||n.type==='user_join_request') return n.title||'';
|
package/src/viewer/server.ts
CHANGED
|
@@ -90,23 +90,6 @@ export function applyMigrationItemToState(state: MigrationStateSnapshot, d: any)
|
|
|
90
90
|
state.success = computeMigrationSuccess(state);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/** Epoch ms for Chunk; OpenClaw SQLite may store Unix seconds or ms. */
|
|
94
|
-
function normalizeTimestamp(value: unknown): number {
|
|
95
|
-
if (value == null) return Date.now();
|
|
96
|
-
if (typeof value === "string") {
|
|
97
|
-
const parsed = Date.parse(value.trim());
|
|
98
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
99
|
-
const n = Number(value);
|
|
100
|
-
if (Number.isFinite(n)) return normalizeTimestamp(n);
|
|
101
|
-
return Date.now();
|
|
102
|
-
}
|
|
103
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
104
|
-
if (value > 0 && value < 5_000_000_000) return Math.round(value * 1000);
|
|
105
|
-
return Math.round(value);
|
|
106
|
-
}
|
|
107
|
-
return Date.now();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
93
|
export interface ViewerServerOptions {
|
|
111
94
|
store: SqliteStore;
|
|
112
95
|
embedder: Embedder;
|
|
@@ -1932,18 +1915,25 @@ export class ViewerServer {
|
|
|
1932
1915
|
|
|
1933
1916
|
private handleRetryJoin(req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
1934
1917
|
this.readBody(req, async (_body) => {
|
|
1935
|
-
if (!this.ctx) return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1918
|
+
if (!this.ctx) return this.jsonResponse(res, { ok: false, error: "sharing_unavailable", errorCode: "sharing_unavailable" });
|
|
1936
1919
|
const sharing = this.ctx.config.sharing;
|
|
1937
1920
|
if (!sharing?.enabled || sharing.role !== "client") {
|
|
1938
|
-
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode" });
|
|
1921
|
+
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode", errorCode: "not_in_client_mode" });
|
|
1939
1922
|
}
|
|
1940
1923
|
const hubAddress = sharing.client?.hubAddress ?? "";
|
|
1941
1924
|
const teamToken = sharing.client?.teamToken ?? "";
|
|
1942
1925
|
if (!hubAddress || !teamToken) {
|
|
1943
|
-
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token" });
|
|
1926
|
+
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token", errorCode: "missing_config" });
|
|
1944
1927
|
}
|
|
1928
|
+
const hubUrl = normalizeHubUrl(hubAddress);
|
|
1929
|
+
|
|
1930
|
+
try {
|
|
1931
|
+
await hubRequestJson(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
1932
|
+
} catch {
|
|
1933
|
+
return this.jsonResponse(res, { ok: false, error: "hub_unreachable", errorCode: "hub_unreachable" });
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1945
1936
|
try {
|
|
1946
|
-
const hubUrl = normalizeHubUrl(hubAddress);
|
|
1947
1937
|
const os = await import("os");
|
|
1948
1938
|
const nickname = sharing.client?.nickname;
|
|
1949
1939
|
const username = nickname || os.userInfo().username || "user";
|
|
@@ -1971,9 +1961,19 @@ export class ViewerServer {
|
|
|
1971
1961
|
lastKnownStatus: result.status || "",
|
|
1972
1962
|
hubInstanceId,
|
|
1973
1963
|
});
|
|
1964
|
+
if (result.status === "blocked") {
|
|
1965
|
+
return this.jsonResponse(res, { ok: false, error: "blocked", errorCode: "blocked" });
|
|
1966
|
+
}
|
|
1974
1967
|
this.jsonResponse(res, { ok: true, status: result.status || "pending" });
|
|
1975
1968
|
} catch (err) {
|
|
1976
|
-
|
|
1969
|
+
const errStr = String(err);
|
|
1970
|
+
if (errStr.includes("(409)") || errStr.includes("username_taken")) {
|
|
1971
|
+
return this.jsonResponse(res, { ok: false, error: "username_taken", errorCode: "username_taken" });
|
|
1972
|
+
}
|
|
1973
|
+
if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
|
|
1974
|
+
return this.jsonResponse(res, { ok: false, error: "invalid_team_token", errorCode: "invalid_team_token" });
|
|
1975
|
+
}
|
|
1976
|
+
this.jsonResponse(res, { ok: false, error: errStr, errorCode: "unknown" });
|
|
1977
1977
|
}
|
|
1978
1978
|
});
|
|
1979
1979
|
}
|
|
@@ -2886,14 +2886,24 @@ export class ViewerServer {
|
|
|
2886
2886
|
const nowClient = Boolean(finalSharing?.enabled) && finalSharing?.role === "client";
|
|
2887
2887
|
const previouslyClient = oldSharingEnabled && oldSharingRole === "client";
|
|
2888
2888
|
let joinStatus: string | undefined;
|
|
2889
|
+
let joinError: string | undefined;
|
|
2889
2890
|
if (nowClient && !previouslyClient) {
|
|
2890
2891
|
try {
|
|
2891
2892
|
joinStatus = await this.autoJoinOnSave(finalSharing);
|
|
2892
2893
|
} catch (e) {
|
|
2893
|
-
|
|
2894
|
+
const msg = String(e instanceof Error ? e.message : e);
|
|
2895
|
+
this.log.warn(`Auto-join on save failed: ${msg}`);
|
|
2896
|
+
if (msg === "hub_unreachable" || msg === "username_taken" || msg === "invalid_team_token") {
|
|
2897
|
+
joinError = msg;
|
|
2898
|
+
}
|
|
2894
2899
|
}
|
|
2895
2900
|
}
|
|
2896
2901
|
|
|
2902
|
+
if (joinError) {
|
|
2903
|
+
this.jsonResponse(res, { ok: true, joinError, restart: false });
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2897
2907
|
this.jsonResponse(res, { ok: true, joinStatus, restart: true });
|
|
2898
2908
|
|
|
2899
2909
|
setTimeout(() => {
|
|
@@ -2914,16 +2924,37 @@ export class ViewerServer {
|
|
|
2914
2924
|
const teamToken = String(clientCfg?.teamToken || "");
|
|
2915
2925
|
if (!hubAddress || !teamToken) return undefined;
|
|
2916
2926
|
const hubUrl = normalizeHubUrl(hubAddress);
|
|
2927
|
+
|
|
2928
|
+
try {
|
|
2929
|
+
await hubRequestJson(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
2930
|
+
} catch {
|
|
2931
|
+
throw new Error("hub_unreachable");
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2917
2934
|
const os = await import("os");
|
|
2918
2935
|
const nickname = String(clientCfg?.nickname || "");
|
|
2919
2936
|
const username = nickname || os.userInfo().username || "user";
|
|
2920
2937
|
const hostname = os.hostname() || "unknown";
|
|
2921
2938
|
const persisted = this.store.getClientHubConnection();
|
|
2922
2939
|
const existingIdentityKey = persisted?.identityKey || "";
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2940
|
+
|
|
2941
|
+
let result: any;
|
|
2942
|
+
try {
|
|
2943
|
+
result = await hubRequestJson(hubUrl, "", "/api/v1/hub/join", {
|
|
2944
|
+
method: "POST",
|
|
2945
|
+
body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
|
|
2946
|
+
});
|
|
2947
|
+
} catch (err) {
|
|
2948
|
+
const errStr = String(err);
|
|
2949
|
+
if (errStr.includes("(409)") || errStr.includes("username_taken")) {
|
|
2950
|
+
throw new Error("username_taken");
|
|
2951
|
+
}
|
|
2952
|
+
if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
|
|
2953
|
+
throw new Error("invalid_team_token");
|
|
2954
|
+
}
|
|
2955
|
+
throw err;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2927
2958
|
const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
|
|
2928
2959
|
let hubInstanceId = persisted?.hubInstanceId || "";
|
|
2929
2960
|
try {
|
|
@@ -3142,14 +3173,43 @@ export class ViewerServer {
|
|
|
3142
3173
|
}
|
|
3143
3174
|
}
|
|
3144
3175
|
} catch {}
|
|
3145
|
-
const
|
|
3176
|
+
const baseUrl = hubUrl.replace(/\/+$/, "");
|
|
3177
|
+
const infoUrl = baseUrl + "/api/v1/hub/info";
|
|
3146
3178
|
const ctrl = new AbortController();
|
|
3147
3179
|
const timeout = setTimeout(() => ctrl.abort(), 8000);
|
|
3148
3180
|
try {
|
|
3149
|
-
const r = await fetch(
|
|
3181
|
+
const r = await fetch(infoUrl, { signal: ctrl.signal });
|
|
3150
3182
|
clearTimeout(timeout);
|
|
3151
3183
|
if (!r.ok) { this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` }); return; }
|
|
3152
3184
|
const info = await r.json() as Record<string, unknown>;
|
|
3185
|
+
|
|
3186
|
+
const { teamToken, nickname } = JSON.parse(body);
|
|
3187
|
+
if (teamToken) {
|
|
3188
|
+
const username = (typeof nickname === "string" && nickname.trim()) || os.userInfo().username || "user";
|
|
3189
|
+
const persisted = this.store.getClientHubConnection();
|
|
3190
|
+
const identityKey = persisted?.identityKey || "";
|
|
3191
|
+
try {
|
|
3192
|
+
const joinR = await fetch(baseUrl + "/api/v1/hub/join", {
|
|
3193
|
+
method: "POST",
|
|
3194
|
+
headers: { "content-type": "application/json" },
|
|
3195
|
+
body: JSON.stringify({ teamToken, username, identityKey, deviceName: os.hostname(), dryRun: true }),
|
|
3196
|
+
});
|
|
3197
|
+
const joinData = await joinR.json() as Record<string, unknown>;
|
|
3198
|
+
if (!joinR.ok && joinData.error === "username_taken") {
|
|
3199
|
+
this.jsonResponse(res, { ok: false, error: "username_taken", teamName: info.teamName || "" });
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
if (!joinR.ok && joinData.error === "invalid_team_token") {
|
|
3203
|
+
this.jsonResponse(res, { ok: false, error: "invalid_team_token", teamName: info.teamName || "" });
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
if (joinR.ok && joinData.status === "blocked") {
|
|
3207
|
+
this.jsonResponse(res, { ok: false, error: "blocked", teamName: info.teamName || "" });
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
} catch { /* join check is best-effort; connection itself is OK */ }
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3153
3213
|
this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
|
|
3154
3214
|
} catch (e: unknown) {
|
|
3155
3215
|
clearTimeout(timeout);
|
|
@@ -3962,8 +4022,8 @@ export class ViewerServer {
|
|
|
3962
4022
|
mergeCount: 0,
|
|
3963
4023
|
lastHitAt: null,
|
|
3964
4024
|
mergeHistory: "[]",
|
|
3965
|
-
createdAt:
|
|
3966
|
-
updatedAt:
|
|
4025
|
+
createdAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
|
|
4026
|
+
updatedAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
|
|
3967
4027
|
};
|
|
3968
4028
|
|
|
3969
4029
|
this.store.insertChunk(chunk);
|