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:
+1
-1
@@ -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
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user