feat(cloud): reconnect after login
This commit is contained in:
parent
06ede45212
commit
bd2bd22069
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "TrapOS",
|
"name": "TrapOS",
|
||||||
"version": "0.12.1",
|
"version": "0.12.2",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"packages": [
|
"packages": [
|
||||||
"trapos"
|
"trapos"
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
"trapos-net": "0.3.2",
|
"trapos-net": "0.3.2",
|
||||||
"trapos-ui": "0.2.4",
|
"trapos-ui": "0.2.4",
|
||||||
"trapos-ai": "0.8.0",
|
"trapos-ai": "0.8.0",
|
||||||
"trapos-cloud": "0.4.1",
|
"trapos-cloud": "0.4.2",
|
||||||
"trapos-sandbox-legacy": "0.3.2",
|
"trapos-sandbox-legacy": "0.3.2",
|
||||||
"trapos": "0.12.1"
|
"trapos": "0.12.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -197,7 +197,8 @@ local function createCloud()
|
|||||||
local queueEvent = opts.queueEvent or function(...) return osLike.queueEvent(...); end;
|
local queueEvent = opts.queueEvent or function(...) return osLike.queueEvent(...); end;
|
||||||
local reconnectDelay = opts.reconnectDelay or 5;
|
local reconnectDelay = opts.reconnectDelay or 5;
|
||||||
local helloTimeout = opts.helloTimeout or 10;
|
local helloTimeout = opts.helloTimeout or 10;
|
||||||
local secret = opts.secret;
|
local getSecret = opts.getSecret or function() return opts.secret; end;
|
||||||
|
local secret = getSecret();
|
||||||
-- Lifecycle logger: log(level, msg, fields?). Defaults to a no-op so the daemon
|
-- Lifecycle logger: log(level, msg, fields?). Defaults to a no-op so the daemon
|
||||||
-- stays silent unless the caller wires a sink (e.g. a /logs file appender).
|
-- stays silent unless the caller wires a sink (e.g. a /logs file appender).
|
||||||
local log = opts.log or function() end;
|
local log = opts.log or function() end;
|
||||||
@ -210,6 +211,8 @@ local function createCloud()
|
|||||||
local state = 'connecting';
|
local state = 'connecting';
|
||||||
local lastError = nil;
|
local lastError = nil;
|
||||||
local activeWs = nil;
|
local activeWs = nil;
|
||||||
|
local suppressNextClose = false;
|
||||||
|
local clearReconnectTimeout = nil;
|
||||||
-- True while looping on retries. Gates the per-attempt chatter (connecting / socket
|
-- True while looping on retries. Gates the per-attempt chatter (connecting / socket
|
||||||
-- open / connection failed / connection closed) so a long outage logs once on entry
|
-- open / connection failed / connection closed) so a long outage logs once on entry
|
||||||
-- and once on recovery instead of flooding the file every reconnectDelay seconds.
|
-- and once on recovery instead of flooding the file every reconnectDelay seconds.
|
||||||
@ -234,6 +237,11 @@ local function createCloud()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function connect()
|
local function connect()
|
||||||
|
if clearReconnectTimeout then
|
||||||
|
clearReconnectTimeout();
|
||||||
|
clearReconnectTimeout = nil;
|
||||||
|
end
|
||||||
|
secret = getSecret();
|
||||||
state = 'connecting';
|
state = 'connecting';
|
||||||
if not reconnecting then
|
if not reconnecting then
|
||||||
log('info', 'connecting', { url = url });
|
log('info', 'connecting', { url = url });
|
||||||
@ -253,7 +261,18 @@ local function createCloud()
|
|||||||
reconnecting = true;
|
reconnecting = true;
|
||||||
log('warn', 'reconnect loop started', { delaySeconds = reconnectDelay });
|
log('warn', 'reconnect loop started', { delaySeconds = reconnectDelay });
|
||||||
end
|
end
|
||||||
el.setTimeout(connect, reconnectDelay);
|
clearReconnectTimeout = el.setTimeout(connect, reconnectDelay);
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reconnectNow(reason)
|
||||||
|
log('info', 'reconnect requested', { reason = reason or 'manual' });
|
||||||
|
lastError = nil;
|
||||||
|
reconnecting = false;
|
||||||
|
if activeWs then
|
||||||
|
suppressNextClose = true;
|
||||||
|
closeActive();
|
||||||
|
end
|
||||||
|
connect();
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onHello(ok, _payload, err)
|
local function onHello(ok, _payload, err)
|
||||||
@ -326,6 +345,11 @@ local function createCloud()
|
|||||||
-- of bricking the daemon into a permanent 'displaced' state.
|
-- of bricking the daemon into a permanent 'displaced' state.
|
||||||
el.register('websocket_closed', function(eventUrl, reason, code)
|
el.register('websocket_closed', function(eventUrl, reason, code)
|
||||||
if eventUrl ~= url then return; end
|
if eventUrl ~= url then return; end
|
||||||
|
if suppressNextClose then
|
||||||
|
suppressNextClose = false;
|
||||||
|
log('info', 'ignored intentional close', { reason = reason, code = code });
|
||||||
|
return;
|
||||||
|
end
|
||||||
if code == DISPLACED_CLOSE_CODE or reason == 'displaced' then
|
if code == DISPLACED_CLOSE_CODE or reason == 'displaced' then
|
||||||
if state == 'ready' then
|
if state == 'ready' then
|
||||||
activeWs = nil;
|
activeWs = nil;
|
||||||
@ -375,6 +399,10 @@ local function createCloud()
|
|||||||
sendFrame(api.buildComputerResponse(traposId, messageId, msgType, ok == true, payload, err));
|
sendFrame(api.buildComputerResponse(traposId, messageId, msgType, ok == true, payload, err));
|
||||||
end);
|
end);
|
||||||
|
|
||||||
|
el.register('trapos_cloud_reconnect', function(reason)
|
||||||
|
reconnectNow(reason);
|
||||||
|
end);
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trapos-cloud",
|
"name": "trapos-cloud",
|
||||||
"version": "0.4.1",
|
"version": "0.4.2",
|
||||||
"description": "TrapOS cloud gateway client: WS daemon (servers/cloud) + cloud command + MCP exec/write server",
|
"description": "TrapOS cloud gateway client: WS daemon (servers/cloud) + cloud command + MCP exec/write server",
|
||||||
"dependencies": ["trapos-core"],
|
"dependencies": ["trapos-core"],
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@ -3,6 +3,7 @@ local createVersion = require('/apis/libversion');
|
|||||||
|
|
||||||
local URL_SETTING = 'cloud.url';
|
local URL_SETTING = 'cloud.url';
|
||||||
local PASSWORD_SETTING = 'cloud.password';
|
local PASSWORD_SETTING = 'cloud.password';
|
||||||
|
local RECONNECT_EVENT = 'trapos_cloud_reconnect';
|
||||||
|
|
||||||
local rawArgs = table.pack(...);
|
local rawArgs = table.pack(...);
|
||||||
local command = rawArgs[1];
|
local command = rawArgs[1];
|
||||||
@ -15,7 +16,7 @@ local function printUsage()
|
|||||||
print('cloud usage:');
|
print('cloud usage:');
|
||||||
print();
|
print();
|
||||||
print(' cloud status');
|
print(' cloud status');
|
||||||
print(' cloud set-password [--force|-f]');
|
print(' cloud login [--force|-f] [password]');
|
||||||
print(' cloud --version');
|
print(' cloud --version');
|
||||||
print(' cloud --help');
|
print(' cloud --help');
|
||||||
print();
|
print();
|
||||||
@ -32,8 +33,9 @@ local function readPassword()
|
|||||||
return '';
|
return '';
|
||||||
end
|
end
|
||||||
|
|
||||||
local function parseSetPasswordArgs(args)
|
local function parseLoginArgs(args)
|
||||||
local force = false;
|
local force = false;
|
||||||
|
local password = nil;
|
||||||
|
|
||||||
for i = 2, args.n do
|
for i = 2, args.n do
|
||||||
local arg = args[i];
|
local arg = args[i];
|
||||||
@ -41,12 +43,14 @@ local function parseSetPasswordArgs(args)
|
|||||||
force = true;
|
force = true;
|
||||||
elseif arg and string.sub(arg, 1, 1) == '-' then
|
elseif arg and string.sub(arg, 1, 1) == '-' then
|
||||||
return nil, 'unknown option: ' .. arg;
|
return nil, 'unknown option: ' .. arg;
|
||||||
|
elseif not password then
|
||||||
|
password = arg;
|
||||||
else
|
else
|
||||||
return nil, 'unexpected argument: ' .. tostring(arg);
|
return nil, 'unexpected argument: ' .. tostring(arg);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { force = force }, nil;
|
return { force = force, password = password }, nil;
|
||||||
end
|
end
|
||||||
|
|
||||||
if command == '--version' or command == '-version' or command == 'version' or command == '-v' then
|
if command == '--version' or command == '-version' or command == 'version' or command == '-v' then
|
||||||
@ -82,8 +86,8 @@ if command == 'status' then
|
|||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
|
|
||||||
if command == 'set-password' then
|
if command == 'login' then
|
||||||
local opts, parseError = parseSetPasswordArgs(rawArgs);
|
local opts, parseError = parseLoginArgs(rawArgs);
|
||||||
if parseError then
|
if parseError then
|
||||||
print(parseError);
|
print(parseError);
|
||||||
printUsage();
|
printUsage();
|
||||||
@ -91,11 +95,11 @@ if command == 'set-password' then
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not opts.force and not isBlank(settings.get(PASSWORD_SETTING)) then
|
if not opts.force and not isBlank(settings.get(PASSWORD_SETTING)) then
|
||||||
print('cloud.password is already set; use cloud set-password --force to replace it');
|
print('cloud.password is already set; use cloud login --force to replace it');
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
|
|
||||||
local password = readPassword();
|
local password = opts.password or readPassword();
|
||||||
if isBlank(password) then
|
if isBlank(password) then
|
||||||
print('cloud.password not changed: password cannot be empty');
|
print('cloud.password not changed: password cannot be empty');
|
||||||
return;
|
return;
|
||||||
@ -103,6 +107,7 @@ if command == 'set-password' then
|
|||||||
|
|
||||||
settings.set(PASSWORD_SETTING, password);
|
settings.set(PASSWORD_SETTING, password);
|
||||||
settings.save();
|
settings.save();
|
||||||
|
os.queueEvent(RECONNECT_EVENT);
|
||||||
print('cloud.password set');
|
print('cloud.password set');
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
|
|||||||
@ -39,14 +39,12 @@ local function fileLog(level, msg, fields)
|
|||||||
f.close();
|
f.close();
|
||||||
end
|
end
|
||||||
|
|
||||||
local secret = settings.get(PASSWORD_SETTING);
|
|
||||||
|
|
||||||
_G.__trapos_cloud_daemon = true;
|
_G.__trapos_cloud_daemon = true;
|
||||||
|
|
||||||
createCloud().startSession({
|
createCloud().startSession({
|
||||||
eventloop = _G.bootEventLoop,
|
eventloop = _G.bootEventLoop,
|
||||||
url = url,
|
url = url,
|
||||||
secret = secret,
|
getSecret = function() return settings.get(PASSWORD_SETTING); end,
|
||||||
os = os,
|
os = os,
|
||||||
log = fileLog,
|
log = fileLog,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -39,6 +39,7 @@ local function runCloud(args, opts)
|
|||||||
local writes = {};
|
local writes = {};
|
||||||
local readMasks = {};
|
local readMasks = {};
|
||||||
local cloudRequests = {};
|
local cloudRequests = {};
|
||||||
|
local queuedEvents = {};
|
||||||
local settingsLib = opts.settings or fakeSettings();
|
local settingsLib = opts.settings or fakeSettings();
|
||||||
local readValues = opts.readValues or {};
|
local readValues = opts.readValues or {};
|
||||||
|
|
||||||
@ -60,6 +61,11 @@ local function runCloud(args, opts)
|
|||||||
set = function(key, value) return settingsLib:set(key, value); end,
|
set = function(key, value) return settingsLib:set(key, value); end,
|
||||||
save = function() return settingsLib:save(); end,
|
save = function() return settingsLib:save(); end,
|
||||||
},
|
},
|
||||||
|
os = setmetatable({
|
||||||
|
queueEvent = function(...)
|
||||||
|
queuedEvents[#queuedEvents + 1] = table.pack(...);
|
||||||
|
end,
|
||||||
|
}, { __index = os }),
|
||||||
require = function(path)
|
require = function(path)
|
||||||
if path == '/apis/libcloud' then
|
if path == '/apis/libcloud' then
|
||||||
return function()
|
return function()
|
||||||
@ -94,16 +100,18 @@ local function runCloud(args, opts)
|
|||||||
writes = writes,
|
writes = writes,
|
||||||
readMasks = readMasks,
|
readMasks = readMasks,
|
||||||
cloudRequests = cloudRequests,
|
cloudRequests = cloudRequests,
|
||||||
|
queuedEvents = queuedEvents,
|
||||||
settings = settingsLib,
|
settings = settingsLib,
|
||||||
};
|
};
|
||||||
end
|
end
|
||||||
|
|
||||||
testlib.test('cloud help lists status and set-password without health', function()
|
testlib.test('cloud help lists status and login without health', function()
|
||||||
local ctx = runCloud({ '--help' });
|
local ctx = runCloud({ '--help' });
|
||||||
|
|
||||||
testlib.assertTrue(containsLine(ctx.lines, ' cloud status'));
|
testlib.assertTrue(containsLine(ctx.lines, ' cloud status'));
|
||||||
testlib.assertTrue(containsLine(ctx.lines, ' cloud set-password [--force|-f]'));
|
testlib.assertTrue(containsLine(ctx.lines, ' cloud login [--force|-f] [password]'));
|
||||||
testlib.assertTrue(not containsLine(ctx.lines, ' cloud health'));
|
testlib.assertTrue(not containsLine(ctx.lines, ' cloud health'));
|
||||||
|
testlib.assertTrue(not containsText(ctx.lines, 'set-password'));
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('cloud health is not kept as an alias', function()
|
testlib.test('cloud health is not kept as an alias', function()
|
||||||
@ -128,9 +136,17 @@ testlib.test('cloud status probes the server', function()
|
|||||||
testlib.assertTrue(containsLine(ctx.lines, 'opencode: missing key'));
|
testlib.assertTrue(containsLine(ctx.lines, 'opencode: missing key'));
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('cloud set-password stores a missing password with masked input', function()
|
testlib.test('cloud set-password is not kept as an alias', function()
|
||||||
|
local ctx = runCloud({ 'set-password', 'hunter2' });
|
||||||
|
|
||||||
|
testlib.assertEquals(ctx.settings.values['cloud.password'], nil);
|
||||||
|
testlib.assertEquals(ctx.settings.saveCount, 0);
|
||||||
|
testlib.assertTrue(containsLine(ctx.lines, 'cloud usage:'));
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('cloud login stores a missing password with masked input', function()
|
||||||
local settingsLib = fakeSettings();
|
local settingsLib = fakeSettings();
|
||||||
local ctx = runCloud({ 'set-password' }, {
|
local ctx = runCloud({ 'login' }, {
|
||||||
settings = settingsLib,
|
settings = settingsLib,
|
||||||
readValues = { 'hunter2' },
|
readValues = { 'hunter2' },
|
||||||
});
|
});
|
||||||
@ -139,12 +155,26 @@ testlib.test('cloud set-password stores a missing password with masked input', f
|
|||||||
testlib.assertEquals(ctx.readMasks[1], '*');
|
testlib.assertEquals(ctx.readMasks[1], '*');
|
||||||
testlib.assertEquals(settingsLib.values['cloud.password'], 'hunter2');
|
testlib.assertEquals(settingsLib.values['cloud.password'], 'hunter2');
|
||||||
testlib.assertEquals(settingsLib.saveCount, 1);
|
testlib.assertEquals(settingsLib.saveCount, 1);
|
||||||
|
testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect');
|
||||||
testlib.assertTrue(containsLine(ctx.lines, 'cloud.password set'));
|
testlib.assertTrue(containsLine(ctx.lines, 'cloud.password set'));
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('cloud set-password rejects replacing without force', function()
|
testlib.test('cloud login stores a password argument without prompting', function()
|
||||||
|
local settingsLib = fakeSettings();
|
||||||
|
local ctx = runCloud({ 'login', 'hunter2' }, {
|
||||||
|
settings = settingsLib,
|
||||||
|
readValues = { 'unused' },
|
||||||
|
});
|
||||||
|
|
||||||
|
testlib.assertEquals(#ctx.readMasks, 0);
|
||||||
|
testlib.assertEquals(settingsLib.values['cloud.password'], 'hunter2');
|
||||||
|
testlib.assertEquals(settingsLib.saveCount, 1);
|
||||||
|
testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect');
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('cloud login rejects replacing without force', function()
|
||||||
local settingsLib = fakeSettings({ ['cloud.password'] = 'old' });
|
local settingsLib = fakeSettings({ ['cloud.password'] = 'old' });
|
||||||
local ctx = runCloud({ 'set-password' }, {
|
local ctx = runCloud({ 'login' }, {
|
||||||
settings = settingsLib,
|
settings = settingsLib,
|
||||||
readValues = { 'new' },
|
readValues = { 'new' },
|
||||||
});
|
});
|
||||||
@ -152,40 +182,44 @@ testlib.test('cloud set-password rejects replacing without force', function()
|
|||||||
testlib.assertEquals(#ctx.readMasks, 0);
|
testlib.assertEquals(#ctx.readMasks, 0);
|
||||||
testlib.assertEquals(settingsLib.values['cloud.password'], 'old');
|
testlib.assertEquals(settingsLib.values['cloud.password'], 'old');
|
||||||
testlib.assertEquals(settingsLib.saveCount, 0);
|
testlib.assertEquals(settingsLib.saveCount, 0);
|
||||||
testlib.assertTrue(containsText(ctx.lines, 'use cloud set-password --force'));
|
testlib.assertEquals(#ctx.queuedEvents, 0);
|
||||||
|
testlib.assertTrue(containsText(ctx.lines, 'use cloud login --force'));
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('cloud set-password --force replaces an existing password', function()
|
testlib.test('cloud login --force replaces an existing password', function()
|
||||||
local settingsLib = fakeSettings({ ['cloud.password'] = 'old' });
|
local settingsLib = fakeSettings({ ['cloud.password'] = 'old' });
|
||||||
runCloud({ 'set-password', '--force' }, {
|
local ctx = runCloud({ 'login', '--force' }, {
|
||||||
settings = settingsLib,
|
settings = settingsLib,
|
||||||
readValues = { 'new' },
|
readValues = { 'new' },
|
||||||
});
|
});
|
||||||
|
|
||||||
testlib.assertEquals(settingsLib.values['cloud.password'], 'new');
|
testlib.assertEquals(settingsLib.values['cloud.password'], 'new');
|
||||||
testlib.assertEquals(settingsLib.saveCount, 1);
|
testlib.assertEquals(settingsLib.saveCount, 1);
|
||||||
|
testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect');
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('cloud set-password -f replaces an existing password', function()
|
testlib.test('cloud login -f replaces an existing password from argument', function()
|
||||||
local settingsLib = fakeSettings({ ['cloud.password'] = 'old' });
|
local settingsLib = fakeSettings({ ['cloud.password'] = 'old' });
|
||||||
runCloud({ 'set-password', '-f' }, {
|
local ctx = runCloud({ 'login', '-f', 'new' }, {
|
||||||
settings = settingsLib,
|
settings = settingsLib,
|
||||||
readValues = { 'new' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testlib.assertEquals(#ctx.readMasks, 0);
|
||||||
testlib.assertEquals(settingsLib.values['cloud.password'], 'new');
|
testlib.assertEquals(settingsLib.values['cloud.password'], 'new');
|
||||||
testlib.assertEquals(settingsLib.saveCount, 1);
|
testlib.assertEquals(settingsLib.saveCount, 1);
|
||||||
|
testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect');
|
||||||
end);
|
end);
|
||||||
|
|
||||||
testlib.test('cloud set-password rejects an empty password', function()
|
testlib.test('cloud login rejects an empty password', function()
|
||||||
local settingsLib = fakeSettings();
|
local settingsLib = fakeSettings();
|
||||||
local ctx = runCloud({ 'set-password' }, {
|
local ctx = runCloud({ 'login' }, {
|
||||||
settings = settingsLib,
|
settings = settingsLib,
|
||||||
readValues = { '' },
|
readValues = { '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
testlib.assertEquals(settingsLib.values['cloud.password'], nil);
|
testlib.assertEquals(settingsLib.values['cloud.password'], nil);
|
||||||
testlib.assertEquals(settingsLib.saveCount, 0);
|
testlib.assertEquals(settingsLib.saveCount, 0);
|
||||||
|
testlib.assertEquals(#ctx.queuedEvents, 0);
|
||||||
testlib.assertTrue(containsLine(ctx.lines, 'cloud.password not changed: password cannot be empty'));
|
testlib.assertTrue(containsLine(ctx.lines, 'cloud.password not changed: password cannot be empty'));
|
||||||
end);
|
end);
|
||||||
|
|
||||||
|
|||||||
@ -269,6 +269,64 @@ testlib.test('startSession reconnects on an abnormal close with a non-displaced
|
|||||||
testlib.assertEquals(#ctx.http.connects, 2);
|
testlib.assertEquals(#ctx.http.connects, 2);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
|
testlib.test('startSession reconnect event reloads the secret immediately', function()
|
||||||
|
local secret = 'old';
|
||||||
|
local ctx = startSession({ getSecret = function() return secret; end });
|
||||||
|
local ws1 = fakes.fakeWs();
|
||||||
|
ctx.el.fire('websocket_success', ctx.url, ws1);
|
||||||
|
local hello1 = textutils.unserializeJSON(ws1.sent[1]);
|
||||||
|
testlib.assertEquals(hello1.payload.secret, 'old');
|
||||||
|
ctx.el.fire('websocket_message', ctx.url, encode({ event = 'server_response', type = 'hello', ok = true, payload = {} }));
|
||||||
|
|
||||||
|
secret = 'new';
|
||||||
|
ctx.el.fire('trapos_cloud_reconnect', 'login');
|
||||||
|
|
||||||
|
testlib.assertEquals(ws1.closed, true);
|
||||||
|
testlib.assertEquals(#ctx.http.connects, 2);
|
||||||
|
local ws2 = fakes.fakeWs();
|
||||||
|
ctx.el.fire('websocket_success', ctx.url, ws2);
|
||||||
|
local hello2 = textutils.unserializeJSON(ws2.sent[1]);
|
||||||
|
testlib.assertEquals(hello2.payload.secret, 'new');
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('startSession reconnect event suppresses the intentional close', function()
|
||||||
|
local ctx = startSession();
|
||||||
|
local ws1 = fakes.fakeWs();
|
||||||
|
ctx.el.fire('websocket_success', ctx.url, ws1);
|
||||||
|
ctx.el.fire('websocket_message', ctx.url, encode({ event = 'server_response', type = 'hello', ok = true, payload = {} }));
|
||||||
|
|
||||||
|
ctx.el.fire('trapos_cloud_reconnect', 'login');
|
||||||
|
local ws2 = fakes.fakeWs();
|
||||||
|
ctx.el.fire('websocket_success', ctx.url, ws2);
|
||||||
|
local timersAfterOpen = #ctx.el.timers;
|
||||||
|
|
||||||
|
ctx.el.fire('websocket_closed', ctx.url, 'manual close');
|
||||||
|
|
||||||
|
testlib.assertEquals(#ctx.el.timers, timersAfterOpen);
|
||||||
|
testlib.assertEquals(#ctx.http.connects, 2);
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('startSession reconnect event recovers after unauthorized', function()
|
||||||
|
local secret = 'old';
|
||||||
|
local ctx = startSession({ getSecret = function() return secret; end });
|
||||||
|
local ws1 = fakes.fakeWs();
|
||||||
|
ctx.el.fire('websocket_success', ctx.url, ws1);
|
||||||
|
ctx.el.fire('websocket_message', ctx.url, encode({
|
||||||
|
event = 'server_response', type = 'hello', ok = false, error = { code = 'unauthorized', message = 'bad secret' },
|
||||||
|
}));
|
||||||
|
testlib.assertEquals(ctx.session.lastError(), 'unauthorized');
|
||||||
|
|
||||||
|
secret = 'new';
|
||||||
|
ctx.el.fire('trapos_cloud_reconnect', 'login');
|
||||||
|
|
||||||
|
testlib.assertEquals(ctx.session.lastError(), nil);
|
||||||
|
testlib.assertEquals(#ctx.http.connects, 2);
|
||||||
|
local ws2 = fakes.fakeWs();
|
||||||
|
ctx.el.fire('websocket_success', ctx.url, ws2);
|
||||||
|
local hello2 = textutils.unserializeJSON(ws2.sent[1]);
|
||||||
|
testlib.assertEquals(hello2.payload.secret, 'new');
|
||||||
|
end);
|
||||||
|
|
||||||
testlib.test('startSession logs the reconnect loop once on entry and once on recovery', function()
|
testlib.test('startSession logs the reconnect loop once on entry and once on recovery', function()
|
||||||
local logs = {};
|
local logs = {};
|
||||||
local function countMsg(msg)
|
local function countMsg(msg)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trapos",
|
"name": "trapos",
|
||||||
"version": "0.12.1",
|
"version": "0.12.2",
|
||||||
"description": "TrapOS full install meta-package",
|
"description": "TrapOS full install meta-package",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"trapos-boot",
|
"trapos-boot",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user