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()