fix: serve the eSIM GET endpoints from _do_GET; release v3.0.0

The three eSIM GET routes were inserted after the POST-only
/api/verify-adm branch, i.e. into the _do_POST chain, so GET
/api/esim/chip|profiles|notifications fell through to the 404 handler
(only POST /api/esim/profile was reachable).

- Moved /api/esim/chip|profiles|notifications into _do_GET (after
  /api/status); /api/esim/profile stays in _do_POST.
- Regression test: inspect the handler sources and assert each route is
  in the right chain.
- Version 3.0.0 (eSIM support is a major update): server/pyproject/PWA
  header, sw.js simple-v227.
This commit is contained in:
2026-09-21 23:27:16 +03:00
parent 69b4caf5d8
commit 563b211e15
5 changed files with 86 additions and 72 deletions
+1 -1
View File
@@ -1436,7 +1436,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 = '2.8.1'; const SIMPLE_VERSION = '3.0.0';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching ===== // ===== Tab switching =====
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v226'; const CACHE = 'simple-v227';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-simple-server" name = "pysim-simple-server"
version = "2.8.1" version = "3.0.0"
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.
+69 -69
View File
@@ -27,7 +27,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad from osmocom.utils import rpad
VERSION = '2.8.1' VERSION = '3.0.0'
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
@@ -3516,6 +3516,74 @@ class PysimHandler(BaseHTTPRequestHandler):
} }
self._send_json(data) self._send_json(data)
self._log_resp(data) self._log_resp(data)
elif self.path == '/api/esim/chip':
app = self.server.app
if not app or not self.server.scc:
self._send_json({'error': _err('reader_not_init', lang)}, 503)
self._log_resp({'error': _err('reader_not_init', lang)})
return
self._log_req()
if not esim.is_euicc(app):
resp = {'error': _err('not_an_euicc', lang)}
self._send_json(resp, 400)
self._log_resp(resp)
return
try:
with _CARD_LOCK:
resp = esim.chip_info(app)
self._send_json(resp)
self._log_resp({'eid': resp.get('eid'), 'errors': resp.get('errors')})
except Exception as e:
resp = {'error': str(e)}
sys.stderr.write('ESIM chip: %s\n' % e)
self._send_json(resp, 500)
self._log_resp(resp)
elif self.path == '/api/esim/profiles':
app = self.server.app
if not app or not self.server.scc:
self._send_json({'error': _err('reader_not_init', lang)}, 503)
self._log_resp({'error': _err('reader_not_init', lang)})
return
self._log_req()
if not esim.is_euicc(app):
resp = {'error': _err('not_an_euicc', lang)}
self._send_json(resp, 400)
self._log_resp(resp)
return
try:
with _CARD_LOCK:
resp = esim.profiles(app)
self._send_json(resp)
self._log_resp({'profiles': len(resp.get('profiles') or []),
'error': resp.get('error')})
except Exception as e:
resp = {'error': str(e)}
sys.stderr.write('ESIM profiles: %s\n' % e)
self._send_json(resp, 500)
self._log_resp(resp)
elif self.path == '/api/esim/notifications':
app = self.server.app
if not app or not self.server.scc:
self._send_json({'error': _err('reader_not_init', lang)}, 503)
self._log_resp({'error': _err('reader_not_init', lang)})
return
self._log_req()
if not esim.is_euicc(app):
resp = {'error': _err('not_an_euicc', lang)}
self._send_json(resp, 400)
self._log_resp(resp)
return
try:
with _CARD_LOCK:
resp = esim.notifications(app)
self._send_json(resp)
self._log_resp({'notifications': len(resp.get('notifications') or []),
'error': resp.get('error')})
except Exception as e:
resp = {'error': str(e)}
sys.stderr.write('ESIM notifications: %s\n' % e)
self._send_json(resp, 500)
self._log_resp(resp)
elif self.path == '/api/commands': elif self.path == '/api/commands':
self._log_req() self._log_req()
app = self.server.app app = self.server.app
@@ -3712,74 +3780,6 @@ class PysimHandler(BaseHTTPRequestHandler):
sys.stderr.write('VERIFY ADM → ERROR: %s\n' % e) sys.stderr.write('VERIFY ADM → ERROR: %s\n' % e)
self._send_json(resp, 500) self._send_json(resp, 500)
self._log_resp(resp) self._log_resp(resp)
elif self.path == '/api/esim/chip':
app = self.server.app
if not app or not self.server.scc:
self._send_json({'error': _err('reader_not_init', lang)}, 503)
self._log_resp({'error': _err('reader_not_init', lang)})
return
self._log_req()
if not esim.is_euicc(app):
resp = {'error': _err('not_an_euicc', lang)}
self._send_json(resp, 400)
self._log_resp(resp)
return
try:
with _CARD_LOCK:
resp = esim.chip_info(app)
self._send_json(resp)
self._log_resp({'eid': resp.get('eid'), 'errors': resp.get('errors')})
except Exception as e:
resp = {'error': str(e)}
sys.stderr.write('ESIM chip: %s\n' % e)
self._send_json(resp, 500)
self._log_resp(resp)
elif self.path == '/api/esim/profiles':
app = self.server.app
if not app or not self.server.scc:
self._send_json({'error': _err('reader_not_init', lang)}, 503)
self._log_resp({'error': _err('reader_not_init', lang)})
return
self._log_req()
if not esim.is_euicc(app):
resp = {'error': _err('not_an_euicc', lang)}
self._send_json(resp, 400)
self._log_resp(resp)
return
try:
with _CARD_LOCK:
resp = esim.profiles(app)
self._send_json(resp)
self._log_resp({'profiles': len(resp.get('profiles') or []),
'error': resp.get('error')})
except Exception as e:
resp = {'error': str(e)}
sys.stderr.write('ESIM profiles: %s\n' % e)
self._send_json(resp, 500)
self._log_resp(resp)
elif self.path == '/api/esim/notifications':
app = self.server.app
if not app or not self.server.scc:
self._send_json({'error': _err('reader_not_init', lang)}, 503)
self._log_resp({'error': _err('reader_not_init', lang)})
return
self._log_req()
if not esim.is_euicc(app):
resp = {'error': _err('not_an_euicc', lang)}
self._send_json(resp, 400)
self._log_resp(resp)
return
try:
with _CARD_LOCK:
resp = esim.notifications(app)
self._send_json(resp)
self._log_resp({'notifications': len(resp.get('notifications') or []),
'error': resp.get('error')})
except Exception as e:
resp = {'error': str(e)}
sys.stderr.write('ESIM notifications: %s\n' % e)
self._send_json(resp, 500)
self._log_resp(resp)
elif self.path == '/api/esim/profile': elif self.path == '/api/esim/profile':
app = self.server.app app = self.server.app
if not app or not self.server.scc: if not app or not self.server.scc:
+14
View File
@@ -6,6 +6,7 @@ monkeypatched with canned responses, so the mapping to the JSON API and the
ISD-R selection/restore logic are tested without hardware. ISD-R selection/restore logic are tested without hardware.
""" """
import inspect
import unittest import unittest
from types import SimpleNamespace from types import SimpleNamespace
@@ -192,5 +193,18 @@ class EsimTests(unittest.TestCase):
esim.chip_info(app) esim.chip_info(app)
class EsimRoutingTests(unittest.TestCase):
def test_esim_routes_are_in_the_right_http_handlers(self):
from pysim_simple_server import server
get_src = inspect.getsource(server.PysimHandler._do_GET)
post_src = inspect.getsource(server.PysimHandler._do_POST)
for route in ('/api/esim/chip', '/api/esim/profiles',
'/api/esim/notifications'):
self.assertIn("self.path == '%s'" % route, get_src, route)
self.assertNotIn("self.path == '%s'" % route, post_src, route)
self.assertIn("self.path == '/api/esim/profile'", post_src)
self.assertNotIn("self.path == '/api/esim/profile'", get_src)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()