fix: animated card icon while the card is being initialized (v3.5.5)
The Card reader view showed the static nosim.svg while the status line said
"Card inserted - initializing..." (the header indicator was correct): the
2 s /api/status poll called pysimSetConnected(false) unconditionally in its
not-connected branch, ignoring the equipping / auto-equip-pending state.
- pysimCardStateUpdate(): one `initializing` flag (equipping || card_present
&& auto_equip) now drives both the status text and the icon
(pysimSetConnected('spin') -> sim_anim.svg), matching the header indicator.
- pysimRefresh() ("Check status"): a reachable server is not an equipped card;
the icon now follows connected / initializing / none instead of always
showing the equipped icon.
- card_state.test.js: initializing states -> 'spin', cardless -> false, and
the four Check-status combinations.
552 frontend / 421 Python green; version 3.5.5; sw cache simple-v258.
This commit is contained in:
+10
-4
@@ -1559,7 +1559,7 @@
|
|||||||
// ===== Version =====
|
// ===== Version =====
|
||||||
// Single source of truth for the PWA version: shown in the header and used
|
// Single source of truth for the PWA version: shown in the header and used
|
||||||
// by the server version check in pysimConnect().
|
// by the server version check in pysimConnect().
|
||||||
const SIMPLE_VERSION = '3.5.4';
|
const SIMPLE_VERSION = '3.5.5';
|
||||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||||
|
|
||||||
// ===== Tab switching =====
|
// ===== Tab switching =====
|
||||||
@@ -6707,7 +6707,10 @@ async function pysimRefresh() {
|
|||||||
const statusEl = document.getElementById('pysim-status');
|
const statusEl = document.getElementById('pysim-status');
|
||||||
try {
|
try {
|
||||||
const data = await pysimFetch('/api/status');
|
const data = await pysimFetch('/api/status');
|
||||||
pysimSetConnected(true);
|
// Server reachable is not the same as card equipped: keep the icon in
|
||||||
|
// step with the card state (animated while it is being initialized).
|
||||||
|
const refreshingInit = !!data.equipping || (!!data.card_present && !!data.auto_equip);
|
||||||
|
pysimSetConnected(data.connected ? true : (refreshingInit ? 'spin' : false));
|
||||||
let html = 'Reader: <b>' + esc(data.reader || 'none') + '</b>' +
|
let html = 'Reader: <b>' + esc(data.reader || 'none') + '</b>' +
|
||||||
' | Card: <b>' + esc(data.card || 'none') + '</b>' +
|
' | Card: <b>' + esc(data.card || 'none') + '</b>' +
|
||||||
' | Profile: <b>' + esc(data.profile || 'none') + '</b><br>' +
|
' | Profile: <b>' + esc(data.profile || 'none') + '</b><br>' +
|
||||||
@@ -9782,11 +9785,14 @@ function pysimCardStateUpdate(status) {
|
|||||||
cardsAutoSelectByIccid(status.iccid);
|
cardsAutoSelectByIccid(status.iccid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pysimSetConnected(false);
|
// A card being initialized (or waiting for auto-equip) shows the animated
|
||||||
|
// icon, matching the header indicator - not the static no-card one.
|
||||||
|
const initializing = status.equipping || (status.card_present && status.auto_equip);
|
||||||
|
pysimSetConnected(initializing ? 'spin' : false);
|
||||||
_cardsAutoIccid = null;
|
_cardsAutoIccid = null;
|
||||||
const statusEl = document.getElementById('pysim-status');
|
const statusEl = document.getElementById('pysim-status');
|
||||||
if (statusEl && statusEl.textContent.trim() !== '') {
|
if (statusEl && statusEl.textContent.trim() !== '') {
|
||||||
if (status.equipping || (status.card_present && status.auto_equip)) {
|
if (initializing) {
|
||||||
statusEl.innerHTML = '<span class="text-gray-500">' + esc(t('Card inserted — initializing...')) + '</span>';
|
statusEl.innerHTML = '<span class="text-gray-500">' + esc(t('Card inserted — initializing...')) + '</span>';
|
||||||
} else if (status.card_present) {
|
} else if (status.card_present) {
|
||||||
statusEl.innerHTML = '<span class="text-amber-600">' + esc(t('Card inserted — press Equip')) + '</span>';
|
statusEl.innerHTML = '<span class="text-amber-600">' + esc(t('Card inserted — press Equip')) + '</span>';
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v257';
|
const CACHE = 'simple-v258';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const path = require('node:path');
|
|||||||
|
|
||||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||||
|
|
||||||
function extractFunc(src, name) {
|
function extractFunc(src, name, asyncFn) {
|
||||||
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||||
const m = re.exec(src);
|
const m = re.exec(src);
|
||||||
if (!m) throw new Error('function ' + name + ' not found');
|
if (!m) throw new Error('function ' + name + ' not found');
|
||||||
@@ -18,7 +18,7 @@ function extractFunc(src, name) {
|
|||||||
if (depth === 0) break;
|
if (depth === 0) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return src.slice(m.index, i + 1);
|
return (asyncFn ? 'async ' : '') + src.slice(m.index, i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let code = 'var _pysimCardStateKey = null;\nvar _pysimCardSession = null;\n'
|
let code = 'var _pysimCardStateKey = null;\nvar _pysimCardSession = null;\n'
|
||||||
@@ -31,6 +31,7 @@ let code = 'var _pysimCardStateKey = null;\nvar _pysimCardSession = null;\n'
|
|||||||
+ 'var _pysimHeaderIccid = undefined;\nvar _pysimHeaderScp80 = undefined;\nvar _pysimHeaderScp81 = undefined;\n'
|
+ 'var _pysimHeaderIccid = undefined;\nvar _pysimHeaderScp80 = undefined;\nvar _pysimHeaderScp81 = undefined;\n'
|
||||||
+ 'var _cardsAutoIccid = null;\nvar _pysimCardIccid = null;\n';
|
+ 'var _cardsAutoIccid = null;\nvar _pysimCardIccid = null;\n';
|
||||||
code += extractFunc(html, 'pysimCardStateUpdate') + '\n';
|
code += extractFunc(html, 'pysimCardStateUpdate') + '\n';
|
||||||
|
code += extractFunc(html, 'pysimRefresh', true) + '\n';
|
||||||
code += extractFunc(html, 'pysimAvailabilityState') + '\n';
|
code += extractFunc(html, 'pysimAvailabilityState') + '\n';
|
||||||
code += extractFunc(html, 'pysimControlDisabled') + '\n';
|
code += extractFunc(html, 'pysimControlDisabled') + '\n';
|
||||||
code += extractFunc(html, 'pysimProactiveSeqChanged') + '\n';
|
code += extractFunc(html, 'pysimProactiveSeqChanged') + '\n';
|
||||||
@@ -130,6 +131,35 @@ test('disconnect with auto-equip shows the initializing message', () => {
|
|||||||
assert.ok(el.innerHTML.includes('initializing'), el.innerHTML);
|
assert.ok(el.innerHTML.includes('initializing'), el.innerHTML);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('initializing states show the animated indicator', () => {
|
||||||
|
for (const extra of [{ card_present: true, equipping: true },
|
||||||
|
{ card_present: true, auto_equip: true }]) {
|
||||||
|
const { calls } = setup();
|
||||||
|
pysimCardStateUpdate(status(extra));
|
||||||
|
assert.deepStrictEqual(calls.connected, ['spin'], JSON.stringify(extra));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a cardless session without auto-equip shows the static no-card icon', () => {
|
||||||
|
const { calls } = setup();
|
||||||
|
pysimCardStateUpdate(status({}));
|
||||||
|
assert.deepStrictEqual(calls.connected, [false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Check status keeps the icon in step with the card state', async () => {
|
||||||
|
for (const entry of [
|
||||||
|
[{ connected: true }, true],
|
||||||
|
[{ connected: false, card_present: true, equipping: true }, 'spin'],
|
||||||
|
[{ connected: false, card_present: true, auto_equip: true }, 'spin'],
|
||||||
|
[{ connected: false }, false],
|
||||||
|
]) {
|
||||||
|
const { calls } = setup();
|
||||||
|
globalThis.pysimFetch = async () => entry[0];
|
||||||
|
await pysimRefresh();
|
||||||
|
assert.deepStrictEqual(calls.connected, [entry[1]], JSON.stringify(entry[0]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('unchanged state key does not touch the UI again', () => {
|
test('unchanged state key does not touch the UI again', () => {
|
||||||
const { el, calls } = setup();
|
const { el, calls } = setup();
|
||||||
pysimCardStateUpdate(status({ card_session: 7 }));
|
pysimCardStateUpdate(status({ card_session: 7 }));
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-simple-server"
|
name = "pysim-simple-server"
|
||||||
version = "3.5.4"
|
version = "3.5.5"
|
||||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from osmocom.tlv import BER_TLV_IE
|
|||||||
from osmocom.utils import rpad
|
from osmocom.utils import rpad
|
||||||
|
|
||||||
|
|
||||||
VERSION = '3.5.4'
|
VERSION = '3.5.5'
|
||||||
|
|
||||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user