diff --git a/frontend/index.html b/frontend/index.html index 2a46ed2..8ecbe3a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1436,7 +1436,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // 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; // ===== Tab switching ===== diff --git a/frontend/sw.js b/frontend/sw.js index b60a1fe..003d4ac 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v226'; +const CACHE = 'simple-v227'; const URLS = [ 'index.html', 'help.html', diff --git a/pyproject.toml b/pyproject.toml index e4dd4fd..de8db60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "2.8.1" +version = "3.0.0" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 6e618e3..07470b2 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -27,7 +27,7 @@ from osmocom.tlv import BER_TLV_IE 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 @@ -3516,6 +3516,74 @@ class PysimHandler(BaseHTTPRequestHandler): } self._send_json(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': self._log_req() app = self.server.app @@ -3712,74 +3780,6 @@ class PysimHandler(BaseHTTPRequestHandler): sys.stderr.write('VERIFY ADM → ERROR: %s\n' % e) self._send_json(resp, 500) 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': app = self.server.app if not app or not self.server.scc: diff --git a/tests/test_esim.py b/tests/test_esim.py index f4c5392..8895d99 100644 --- a/tests/test_esim.py +++ b/tests/test_esim.py @@ -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. """ +import inspect import unittest from types import SimpleNamespace @@ -192,5 +193,18 @@ class EsimTests(unittest.TestCase): 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__': unittest.main()