Compare commits
2 Commits
a5cccee17c
...
dfb9551eb7
| Author | SHA1 | Date | |
|---|---|---|---|
| dfb9551eb7 | |||
| 8459040856 |
+1
-1
@@ -55,7 +55,7 @@ Returns server version for compatibility checking.
|
||||
|
||||
**Example response:**
|
||||
```json
|
||||
{"version": "2.1.0"}
|
||||
{"version": "2.1.1"}
|
||||
```
|
||||
|
||||
### `GET /api/status`
|
||||
|
||||
+28
-4
@@ -18,7 +18,7 @@
|
||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.1.0</span></h1>
|
||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.1.1</span></h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
||||
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
||||
@@ -6713,6 +6713,10 @@ function pysimCardStateUpdate(status) {
|
||||
_pysimCardEquipped = !!status.connected;
|
||||
_pysimEquipping = !!status.equipping;
|
||||
pysimApplyAvailability();
|
||||
if (pysimProactiveSeqChanged(status.proactive_seq)
|
||||
&& isViewVisible('tab-phone') && isViewVisible('phone-sub-phone')) {
|
||||
pysimProactiveLogRender();
|
||||
}
|
||||
const key = [status.connected, !!status.card_present, !!status.equipping, !!status.auto_equip, status.card_session].join('|');
|
||||
if (key === _pysimCardStateKey) return;
|
||||
_pysimCardStateKey = key;
|
||||
@@ -6752,17 +6756,35 @@ function pysimStartBackendPoll() {
|
||||
}, 2000);
|
||||
_pysimPollTimer = setInterval(async () => {
|
||||
try {
|
||||
const [log, stk, ps] = await Promise.all([
|
||||
pysimFetch('/api/proactive-log'),
|
||||
const [stk, ps] = await Promise.all([
|
||||
pysimFetch('/api/stk-status'),
|
||||
pysimFetch('/api/poll-status'),
|
||||
]);
|
||||
pysimUpdatePollUI(ps.enabled, ps.interval);
|
||||
if (pysimStkStatusChanged(stk) && isViewVisible('tab-phone')) stkCheckMenu();
|
||||
} catch (e) { /* ignore */ }
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
let _pysimStkSig = null;
|
||||
|
||||
function pysimStkStatusChanged(stk) {
|
||||
if (!stk) return false;
|
||||
const sig = [!!stk.active, !!stk.pending, stk.pending_type || ''].join('|');
|
||||
if (sig === _pysimStkSig) return false;
|
||||
_pysimStkSig = sig;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== proactive command log =====
|
||||
let _pysimProactiveSeq = null;
|
||||
|
||||
function pysimProactiveSeqChanged(seq) {
|
||||
if (seq === undefined || seq === null) return false;
|
||||
if (_pysimProactiveSeq === seq) return false;
|
||||
_pysimProactiveSeq = seq;
|
||||
return true;
|
||||
}
|
||||
const CMD_NAMES = {
|
||||
'03': 'POLL INTERVAL', '05': 'SET UP EVENT LIST',
|
||||
'13': 'SEND SHORT MESSAGE', '20': 'PLAY TONE',
|
||||
@@ -6818,7 +6840,8 @@ function pysimLogFields(decoded) {
|
||||
|
||||
async function pysimProactiveLogRender() {
|
||||
const el = document.getElementById('pysim-proactive-log');
|
||||
el.innerHTML = '<span class="text-gray-400">' + t('Loading...') + '</span>';
|
||||
const scrollTop = el.scrollTop;
|
||||
if (!el.firstChild) el.innerHTML = '<span class="text-gray-400">' + t('Loading...') + '</span>';
|
||||
try {
|
||||
const log = await pysimFetch('/api/proactive-log');
|
||||
if (!log || !log.length) {
|
||||
@@ -6865,6 +6888,7 @@ async function pysimProactiveLogRender() {
|
||||
html += '</div></div>';
|
||||
});
|
||||
el.innerHTML = html;
|
||||
el.scrollTop = scrollTop;
|
||||
} catch (err) {
|
||||
el.innerHTML = '<span class="text-red-500">Error: ' + esc(err.message) + '</span>';
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'otaman-v123';
|
||||
const CACHE = 'otaman-v125';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -22,23 +22,29 @@ function extractFunc(src, name) {
|
||||
}
|
||||
|
||||
let code = 'var _pysimCardStateKey = null;\nvar _pysimCardSession = null;\n'
|
||||
+ 'var _pysimServerAvailable = null;\nvar _pysimCardEquipped = false;\n';
|
||||
+ 'var _pysimServerAvailable = null;\nvar _pysimCardEquipped = false;\n'
|
||||
+ 'var _pysimProactiveSeq = null;\nvar _pysimStkSig = null;\n';
|
||||
code += extractFunc(html, 'pysimCardStateUpdate') + '\n';
|
||||
code += extractFunc(html, 'pysimAvailabilityState') + '\n';
|
||||
code += extractFunc(html, 'pysimControlDisabled') + '\n';
|
||||
code += extractFunc(html, 'pysimProactiveSeqChanged') + '\n';
|
||||
code += extractFunc(html, 'pysimStkStatusChanged') + '\n';
|
||||
code += '\nglobalThis.esc = s => s;\n';
|
||||
code += 'globalThis.t = s => s;\n';
|
||||
eval(code);
|
||||
|
||||
function setup() {
|
||||
const el = { textContent: 'status line', innerHTML: '' };
|
||||
const calls = { connected: [], resets: [], refreshStatus: [] };
|
||||
const calls = { connected: [], resets: [], refreshStatus: [], proactive: 0 };
|
||||
_pysimCardStateKey = null;
|
||||
_pysimCardSession = null;
|
||||
_pysimProactiveSeq = null;
|
||||
globalThis.document = { getElementById: () => el, querySelectorAll: () => [] };
|
||||
globalThis.pysimSetConnected = v => calls.connected.push(v);
|
||||
globalThis.pysimResetCardData = refresh => calls.resets.push(refresh);
|
||||
globalThis.pysimApplyAvailability = () => {};
|
||||
globalThis.isViewVisible = () => true;
|
||||
globalThis.pysimProactiveLogRender = () => { calls.proactive++; };
|
||||
return { el, calls };
|
||||
}
|
||||
|
||||
@@ -127,3 +133,38 @@ test('no card with auto-equip enabled still shows the no-card message', () => {
|
||||
assert.ok(el.innerHTML.includes('No card detected'), el.innerHTML);
|
||||
assert.ok(!el.innerHTML.includes('initializing'), el.innerHTML);
|
||||
});
|
||||
|
||||
test('proactive log refreshes when the status sequence changes', () => {
|
||||
const { calls } = setup();
|
||||
pysimCardStateUpdate(status({ proactive_seq: 7 }));
|
||||
pysimCardStateUpdate(status({ proactive_seq: 7 }));
|
||||
assert.strictEqual(calls.proactive, 1);
|
||||
pysimCardStateUpdate(status({ proactive_seq: 8 }));
|
||||
assert.strictEqual(calls.proactive, 2);
|
||||
});
|
||||
|
||||
test('proactive log is not refreshed while the phone view is hidden', () => {
|
||||
const { calls } = setup();
|
||||
globalThis.isViewVisible = () => false;
|
||||
pysimCardStateUpdate(status({ proactive_seq: 3 }));
|
||||
assert.strictEqual(calls.proactive, 0);
|
||||
});
|
||||
|
||||
test('pysimProactiveSeqChanged tracks the last sequence', () => {
|
||||
_pysimProactiveSeq = null;
|
||||
assert.ok(pysimProactiveSeqChanged(4));
|
||||
assert.ok(!pysimProactiveSeqChanged(4));
|
||||
assert.ok(pysimProactiveSeqChanged(5));
|
||||
assert.ok(!pysimProactiveSeqChanged(undefined));
|
||||
assert.ok(!pysimProactiveSeqChanged(null));
|
||||
});
|
||||
|
||||
test('pysimStkStatusChanged detects menu state transitions', () => {
|
||||
_pysimStkSig = null;
|
||||
assert.ok(pysimStkStatusChanged({ active: false, pending: false }));
|
||||
assert.ok(!pysimStkStatusChanged({ active: false, pending: false }));
|
||||
assert.ok(pysimStkStatusChanged({ active: true, pending: true, pending_type: 'select_item' }));
|
||||
assert.ok(!pysimStkStatusChanged({ active: true, pending: true, pending_type: 'select_item' }));
|
||||
assert.ok(pysimStkStatusChanged({ active: true, pending: false }));
|
||||
assert.ok(!pysimStkStatusChanged(null));
|
||||
});
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-otaman-server"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -19,7 +19,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
||||
from osmocom.tlv import BER_TLV_IE
|
||||
|
||||
|
||||
VERSION = '2.1.0'
|
||||
VERSION = '2.1.1'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -1821,6 +1821,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
'connected': connected,
|
||||
'card_present': bool(getattr(self.server, 'card_present', False)),
|
||||
'card_session': int(getattr(self.server, 'card_session', 0)),
|
||||
'proactive_seq': _PROACTIVE_ENTRY_ID,
|
||||
'equipping': bool(getattr(self.server, 'equipping', False)),
|
||||
'auto_equip': bool(_AUTO_EQUIP),
|
||||
'card': card.name if card else None,
|
||||
|
||||
Reference in New Issue
Block a user