esim: show the profile icon image and its data size
pySim already requests the Icon tag (0x94) together with the other ProfileInfo tags; the mapping dropped it, so the PWA only had the icon type. - esim.profiles(): each profile now carries `icon` (the image bytes as hex) and `icon_size` (byte count), null when the card sent no image. - PWA: esimIconDataUrl() builds a data: URL (png/jpg -> image/png|jpeg) and the profile card shows the image unscaled to the left of the metadata rows; the Icon row keeps the type and adds the data size (e.g. 'png · 1234 B'). - tests: profiles icon/icon_size mapping (+ the absent case) and the frontend data URL / row / render checks; docs and sw simple-v231.
This commit is contained in:
@@ -598,7 +598,7 @@ Values persist on the server until restart. Apply → hex updates; Save → POST
|
|||||||
|
|
||||||
**Network state monitor** — a compact **Network state** panel next to the simulation buttons shows what the card currently holds and what was last simulated. Its header carries the **simulated service state** (*Undefined* until a scenario or a Location status event sets it, then *Normal service* / *Limited service* / *No service*) with a *PLMN not allowed* marker when the location files or EF.FPLMN show a rejection, plus the current location: PLMN, country and operator, the LAI/RAI/TAI, and the **roaming class** (*Home* when the PLMN equals the HPLMN, *Home equivalent* when it is in EF.EHPLMN, otherwise *Guest*). Below it, one compact line per monitored file (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) with its decoded summary and how it was last updated (`init`, `write`, `read`, `refresh`); hover for the full decoded fields — long PLMN lists are abbreviated (EF.HPLMNwAcT shows only the first network plus a `… +N` counter). The panel reads the files once at equip (only when the ICCID was readable), updates them in place from the bytes the simulator wrote, re-reads EF.IMSI after every scenario and Location-status event, and never polls the card — **Refresh** re-reads all files on demand.
|
**Network state monitor** — a compact **Network state** panel next to the simulation buttons shows what the card currently holds and what was last simulated. Its header carries the **simulated service state** (*Undefined* until a scenario or a Location status event sets it, then *Normal service* / *Limited service* / *No service*) with a *PLMN not allowed* marker when the location files or EF.FPLMN show a rejection, plus the current location: PLMN, country and operator, the LAI/RAI/TAI, and the **roaming class** (*Home* when the PLMN equals the HPLMN, *Home equivalent* when it is in EF.EHPLMN, otherwise *Guest*). Below it, one compact line per monitored file (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) with its decoded summary and how it was last updated (`init`, `write`, `read`, `refresh`); hover for the full decoded fields — long PLMN lists are abbreviated (EF.HPLMNwAcT shows only the first network plus a `… +N` counter). The panel reads the files once at equip (only when the ICCID was readable), updates them in place from the bytes the simulator wrote, re-reads EF.IMSI after every scenario and Location-status event, and never polls the card — **Refresh** re-reads all files on demand.
|
||||||
|
|
||||||
**eSIM** — for an eUICC (SGP.22/SGP.32) the **eSIM** pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact): **Chip** (EID, EUICCInfo1/2 with decoded capability bit lists, card resources, CI PKI identifiers, category, forbidden PPRs, certification data; configured default SM-DP+ / root DS addresses; the rules authorisation table from ES10b GetRat), **Profiles** (state, nickname, provider, ICCID, ISD-P AID, class, owner; **Enable**/**Disable** switches a profile — the card usually sends REFRESH first and the card session is then re-initialized like an equip, so the ICCID, network state and every cached card view are re-read, and the requested state is verified against the re-read profile list) and **Notifications** (read-only pending list). No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used; a non-eUICC card is reported as such.
|
**eSIM** — for an eUICC (SGP.22/SGP.32) the **eSIM** pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact): **Chip** (EID, EUICCInfo1/2 with decoded capability bit lists, card resources, CI PKI identifiers, category, forbidden PPRs, certification data; configured default SM-DP+ / root DS addresses; the rules authorisation table from ES10b GetRat), **Profiles** (state, nickname, provider, ICCID, ISD-P AID, class, owner, and the icon image at its natural size with its type/byte size; **Enable**/**Disable** switches a profile — the card usually sends REFRESH first and the card session is then re-initialized like an equip, so the ICCID, network state and every cached card view are re-read, and the requested state is verified against the re-read profile list) and **Notifications** (read-only pending list). No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used; a non-eUICC card is reported as such.
|
||||||
|
|
||||||
## SCP81
|
## SCP81
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -569,7 +569,7 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
|
|||||||
|
|
||||||
**Монитор сетевого состояния** — компактная панель **«Сетевое состояние»** рядом с кнопками симуляции показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — **сэмулированное состояние сервиса** (*Не определено*, пока его не задаст сценарий или событие Location status, затем *Обычный сервис* / *Ограниченный сервис* / *Нет сервиса*) с пометкой *PLMN не разрешён*, если location-файлы или EF.FPLMN указывают на отказ, плюс текущее местоположение: PLMN, страна и оператор, LAI/RAI/TAI и **класс роуминга** (*Домашняя сеть*, если PLMN совпадает с HPLMN; *Эквивалентная домашней*, если он есть в EF.EHPLMN; иначе *Гостевая (роуминг)*). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (`init`, `write`, `read`, `refresh`); при наведении — все декодированные поля; длинные списки PLMN сокращаются (EF.HPLMNwAcT показывает только первую сеть и пометку `… +N`). Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status и никогда не опрашивает карту — кнопка **«Обновить»** перечитывает все файлы по требованию.
|
**Монитор сетевого состояния** — компактная панель **«Сетевое состояние»** рядом с кнопками симуляции показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — **сэмулированное состояние сервиса** (*Не определено*, пока его не задаст сценарий или событие Location status, затем *Обычный сервис* / *Ограниченный сервис* / *Нет сервиса*) с пометкой *PLMN не разрешён*, если location-файлы или EF.FPLMN указывают на отказ, плюс текущее местоположение: PLMN, страна и оператор, LAI/RAI/TAI и **класс роуминга** (*Домашняя сеть*, если PLMN совпадает с HPLMN; *Эквивалентная домашней*, если он есть в EF.EHPLMN; иначе *Гостевая (роуминг)*). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (`init`, `write`, `read`, `refresh`); при наведении — все декодированные поля; длинные списки PLMN сокращаются (EF.HPLMNwAcT показывает только первую сеть и пометку `… +N`). Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status и никогда не опрашивает карту — кнопка **«Обновить»** перечитывает все файлы по требованию.
|
||||||
|
|
||||||
**eSIM** — для eUICC (SGP.22/SGP.32) подвкладка **«eSIM»** читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+): **Chip** (EID, EUICCInfo1/2 с декодированными битовыми списками возможностей, ресурсами карты, идентификаторами CI PKI, категорией, запрещёнными PPR, данными сертификации; настроенные адреса SM-DP+ по умолчанию / корневого DS; таблица авторизации правил из ES10b GetRat), **Profiles** (состояние, nickname, провайдер, ICCID, AID ISD-P, класс, владелец; **Enable**/**Disable** переключает профиль — карта обычно сначала присылает REFRESH, после чего сессия карты переинициализируется как при equip, и ICCID, сетевое состояние и все кэшированные представления перечитываются, а запрошенное состояние проверяется по перечитанному списку профилей) и **Notifications** (список ожидающих уведомлений, только чтение). Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c; для карты не-eUICC подвкладка сообщает об этом.
|
**eSIM** — для eUICC (SGP.22/SGP.32) подвкладка **«eSIM»** читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+): **Chip** (EID, EUICCInfo1/2 с декодированными битовыми списками возможностей, ресурсами карты, идентификаторами CI PKI, категорией, запрещёнными PPR, данными сертификации; настроенные адреса SM-DP+ по умолчанию / корневого DS; таблица авторизации правил из ES10b GetRat), **Profiles** (состояние, nickname, провайдер, ICCID, AID ISD-P, класс, владелец и значок профиля в натуральную величину с типом/размером в байтах; **Enable**/**Disable** переключает профиль — карта обычно сначала присылает REFRESH, после чего сессия карты переинициализируется как при equip, и ICCID, сетевое состояние и все кэшированные представления перечитываются, а запрошенное состояние проверяется по перечитанному списку профилей) и **Notifications** (список ожидающих уведомлений, только чтение). Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c; для карты не-eUICC подвкладка сообщает об этом.
|
||||||
|
|
||||||
## SCP81
|
## SCP81
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -225,7 +225,11 @@ lock; `GET /api/status` reports `euicc` and `eid` for the PWA.
|
|||||||
- `GET /api/esim/profiles` — `{"profiles": [{"iccid": "8970…",
|
- `GET /api/esim/profiles` — `{"profiles": [{"iccid": "8970…",
|
||||||
"isdp_aid": "A000…", "state": "enabled"|"disabled", "nickname": …,
|
"isdp_aid": "A000…", "state": "enabled"|"disabled", "nickname": …,
|
||||||
"provider": …, "name": …, "class": "test"|"provisioning"|"operational",
|
"provider": …, "name": …, "class": "test"|"provisioning"|"operational",
|
||||||
"owner": "250-99", "icon_type": "png"|"jpg"}], "error": null}`.
|
"owner": "250-99", "icon_type": "png"|"jpg", "icon": "89504E47…",
|
||||||
|
"icon_size": 1234}], "error": null}`. `icon` is the SGP.22 icon data
|
||||||
|
(tag `0x94`) as hex and `icon_size` its byte count, so a client can show
|
||||||
|
the profile image (`data:image/<type>;base64,…`); both are `null` when the
|
||||||
|
card sent no icon.
|
||||||
- `GET /api/esim/notifications` — `{"notifications": [{"seq_number": 3,
|
- `GET /api/esim/notifications` — `{"notifications": [{"seq_number": 3,
|
||||||
"operations": ["enable"], "address": "smdp.example.org",
|
"operations": ["enable"], "address": "smdp.example.org",
|
||||||
"iccid": "8970…"}], "error": null}`.
|
"iccid": "8970…"}], "error": null}`.
|
||||||
|
|||||||
@@ -498,7 +498,7 @@
|
|||||||
<p class="text-sm mb-2">Для eUICC (SGP.22/SGP.32) подвкладка <strong>«eSIM»</strong> читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+):</p>
|
<p class="text-sm mb-2">Для eUICC (SGP.22/SGP.32) подвкладка <strong>«eSIM»</strong> читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+):</p>
|
||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Чип</strong> — EID, EUICCInfo1/2 со всеми декодированными полями (версия профиля, SVN, прошивка, расширенные ресурсы карты — число установленных приложений и свободная энергонезависимая/энергозависимая память —, битовые списки возможностей UICC и RSP, идентификаторы CI PKI, категория, запрещённые PPR, версия PP, номер аккредитации SS, данные сертификации), настроенные адреса SM-DP+ по умолчанию / корневого DS и <strong>таблица авторизации правил</strong> (ES10b GetRat: идентификаторы PPR, разрешённые операторы, флаг согласия). Неизвестные TLV сохраняются как сырой hex.</li>
|
<li><strong>Чип</strong> — EID, EUICCInfo1/2 со всеми декодированными полями (версия профиля, SVN, прошивка, расширенные ресурсы карты — число установленных приложений и свободная энергонезависимая/энергозависимая память —, битовые списки возможностей UICC и RSP, идентификаторы CI PKI, категория, запрещённые PPR, версия PP, номер аккредитации SS, данные сертификации), настроенные адреса SM-DP+ по умолчанию / корневого DS и <strong>таблица авторизации правил</strong> (ES10b GetRat: идентификаторы PPR, разрешённые операторы, флаг согласия). Неизвестные TLV сохраняются как сырой hex.</li>
|
||||||
<li><strong>Профили</strong> — все профили с состоянием (включён/отключён), ником, оператором, именем, ICCID, ISD-P AID, классом, владельцем и типом значка. Кнопки <strong>«Включить»</strong>/<strong>«Отключить»</strong> переключают профиль; карта обычно сначала отправляет REFRESH (виден в журнале проактивных команд) — терминальный ответ завершает переключение, после чего сессия карты переинициализируется как при equip: ICCID, сетевое состояние и все кэшированные данные перечитываются, а запрошенное состояние проверяется по перечитанному списку профилей.</li>
|
<li><strong>Профили</strong> — все профили с состоянием (включён/отключён), ником, оператором, именем, ICCID, ISD-P AID, классом, владельцем и типом значка — сам значок показывается в натуральную величину рядом с метаданными, а строка <strong>Icon</strong> содержит его тип и размер данных в байтах. Кнопки <strong>«Включить»</strong>/<strong>«Отключить»</strong> переключают профиль; карта обычно сначала отправляет REFRESH (виден в журнале проактивных команд) — терминальный ответ завершает переключение, после чего сессия карты переинициализируется как при equip: ICCID, сетевое состояние и все кэшированные данные перечитываются, а запрошенное состояние проверяется по перечитанному списку профилей.</li>
|
||||||
<li><strong>Уведомления</strong> — список ожидающих уведомлений только для чтения (номер последовательности, операция, адрес, ICCID); ничего не обрабатывается и не удаляется.</li>
|
<li><strong>Уведомления</strong> — список ожидающих уведомлений только для чтения (номер последовательности, операция, адрес, ICCID); ничего не обрабатывается и не удаляется.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-sm mb-3">Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c. Если карта не eUICC, подвкладка сообщает об этом.</p>
|
<p class="text-sm mb-3">Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c. Если карта не eUICC, подвкладка сообщает об этом.</p>
|
||||||
|
|||||||
+1
-1
@@ -498,7 +498,7 @@
|
|||||||
<p class="text-sm mb-2">For an eUICC (SGP.22/SGP.32) the <strong>eSIM</strong> pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact):</p>
|
<p class="text-sm mb-2">For an eUICC (SGP.22/SGP.32) the <strong>eSIM</strong> pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact):</p>
|
||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Chip</strong> — EID, EUICCInfo1/2 with every field decoded (profile version, SVN, firmware, extended card resource — installed applications and free non-volatile/volatile memory —, UICC and RSP capability bit lists, CI PKI identifiers, category, forbidden profile policy rules, PP version, SS accreditation number, certification data), the configured default SM-DP+ / root DS addresses, and the <strong>rules authorisation table</strong> (ES10b GetRat: PPR IDs, allowed operators, consent flag). Unknown TLVs are preserved as raw hex.</li>
|
<li><strong>Chip</strong> — EID, EUICCInfo1/2 with every field decoded (profile version, SVN, firmware, extended card resource — installed applications and free non-volatile/volatile memory —, UICC and RSP capability bit lists, CI PKI identifiers, category, forbidden profile policy rules, PP version, SS accreditation number, certification data), the configured default SM-DP+ / root DS addresses, and the <strong>rules authorisation table</strong> (ES10b GetRat: PPR IDs, allowed operators, consent flag). Unknown TLVs are preserved as raw hex.</li>
|
||||||
<li><strong>Profiles</strong> — every profile with its state (enabled/disabled), nickname, provider, name, ICCID, ISD-P AID, class, owner and icon type. <strong>Enable</strong>/<strong>Disable</strong> switches a profile; the card usually sends REFRESH first (logged in the proactive command log) — the terminal response completes the switch and the card session is then re-initialized like an equip, so the ICCID, network state and every cached card view are re-read, and the requested state is verified against the re-read profile list.</li>
|
<li><strong>Profiles</strong> — every profile with its state (enabled/disabled), nickname, provider, name, ICCID, ISD-P AID, class, owner and icon type — the icon image itself is shown at its natural size next to the metadata, and the <strong>Icon</strong> row carries its type and data size in bytes. <strong>Enable</strong>/<strong>Disable</strong> switches a profile; the card usually sends REFRESH first (logged in the proactive command log) — the terminal response completes the switch and the card session is then re-initialized like an equip, so the ICCID, network state and every cached card view are re-read, and the requested state is verified against the re-read profile list.</li>
|
||||||
<li><strong>Notifications</strong> — read-only list of pending notifications (sequence number, operation, address, ICCID); nothing is processed or removed.</li>
|
<li><strong>Notifications</strong> — read-only list of pending notifications (sequence number, operation, address, ICCID); nothing is processed or removed.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-sm mb-3">No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used. The card must be an eUICC; otherwise the pill says so.</p>
|
<p class="text-sm mb-3">No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used. The card must be an eUICC; otherwise the pill says so.</p>
|
||||||
|
|||||||
+26
-2
@@ -1644,10 +1644,27 @@ function esimProfileRows(p) {
|
|||||||
if (p.name) rows.push([t('Profile name'), p.name]);
|
if (p.name) rows.push([t('Profile name'), p.name]);
|
||||||
if (p.class) rows.push([t('Class'), p.class]);
|
if (p.class) rows.push([t('Class'), p.class]);
|
||||||
if (p.owner) rows.push([t('Owner'), p.owner]);
|
if (p.owner) rows.push([t('Owner'), p.owner]);
|
||||||
if (p.icon_type) rows.push([t('Icon'), p.icon_type]);
|
if (p.icon_type) {
|
||||||
|
let icon = p.icon_type;
|
||||||
|
if (p.icon_size) icon += ' · ' + p.icon_size + ' B';
|
||||||
|
rows.push([t('Icon'), icon]);
|
||||||
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Profile icon -> data URL at its natural size ('' when the card sent no
|
||||||
|
// image). The icon type is the SGP.22 iconType: png or jpg.
|
||||||
|
function esimIconDataUrl(p) {
|
||||||
|
if (!p || !p.icon || !p.icon_type) return '';
|
||||||
|
const hex = String(p.icon).replace(/[^0-9a-fA-F]/g, '');
|
||||||
|
let bin = '';
|
||||||
|
for (let i = 0; i + 1 < hex.length; i += 2) {
|
||||||
|
bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
||||||
|
}
|
||||||
|
const mime = p.icon_type === 'jpg' ? 'image/jpeg' : 'image/' + p.icon_type;
|
||||||
|
return 'data:' + mime + ';base64,' + btoa(bin);
|
||||||
|
}
|
||||||
|
|
||||||
function esimRenderChip() {
|
function esimRenderChip() {
|
||||||
const el = document.getElementById('esim-chip');
|
const el = document.getElementById('esim-chip');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -1705,10 +1722,17 @@ function esimRenderProfiles() {
|
|||||||
'class="ml-auto px-2 py-0.5 text-xs rounded text-white disabled:opacity-40 disabled:cursor-not-allowed ' +
|
'class="ml-auto px-2 py-0.5 text-xs rounded text-white disabled:opacity-40 disabled:cursor-not-allowed ' +
|
||||||
(enabled ? 'bg-gray-600 hover:bg-gray-700' : 'bg-emerald-600 hover:bg-emerald-700') + '">' +
|
(enabled ? 'bg-gray-600 hover:bg-gray-700' : 'bg-emerald-600 hover:bg-emerald-700') + '">' +
|
||||||
esc(t(enabled ? 'Disable' : 'Enable')) + '</button></div>';
|
esc(t(enabled ? 'Disable' : 'Enable')) + '</button></div>';
|
||||||
|
let rowsHtml = '';
|
||||||
for (const [k, v] of esimProfileRows(p)) {
|
for (const [k, v] of esimProfileRows(p)) {
|
||||||
html += '<div class="flex gap-2"><span class="w-40 shrink-0 text-right text-gray-500 dark:text-slate-400 break-all">' + esc(k) + '</span>' +
|
rowsHtml += '<div class="flex gap-2"><span class="w-40 shrink-0 text-right text-gray-500 dark:text-slate-400 break-all">' + esc(k) + '</span>' +
|
||||||
'<span class="font-mono break-all">' + esc(v) + '</span></div>';
|
'<span class="font-mono break-all">' + esc(v) + '</span></div>';
|
||||||
}
|
}
|
||||||
|
const iconUrl = esimIconDataUrl(p);
|
||||||
|
html += '<div class="flex gap-3 items-start">';
|
||||||
|
if (iconUrl) {
|
||||||
|
html += '<img src="' + esc(iconUrl) + '" alt="" class="shrink-0">';
|
||||||
|
}
|
||||||
|
html += '<div class="min-w-0 flex-1">' + rowsHtml + '</div></div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
el.innerHTML = html;
|
el.innerHTML = html;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v230';
|
const CACHE = 'simple-v231';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ function extractFunc(src, name) {
|
|||||||
|
|
||||||
let code = '';
|
let code = '';
|
||||||
for (const fn of ['esimLabel', 'esimGroupEid', 'esimFieldRows', 'esimResultText',
|
for (const fn of ['esimLabel', 'esimGroupEid', 'esimFieldRows', 'esimResultText',
|
||||||
'esimStateLabel', 'esimOperationsText', 'esimProfileRows', 'esimSwitchStatus']) {
|
'esimStateLabel', 'esimOperationsText', 'esimProfileRows', 'esimIconDataUrl',
|
||||||
|
'esimSwitchStatus']) {
|
||||||
code += extractFunc(html, fn) + '\n';
|
code += extractFunc(html, fn) + '\n';
|
||||||
}
|
}
|
||||||
for (const c of ['ESIM_CHIP_LABELS', 'ESIM_RESULT_KEYS']) {
|
for (const c of ['ESIM_CHIP_LABELS', 'ESIM_RESULT_KEYS']) {
|
||||||
@@ -124,11 +125,32 @@ test('esimProfileRows renders the metadata in a stable order', () => {
|
|||||||
const rows = esimProfileRows({
|
const rows = esimProfileRows({
|
||||||
iccid: '8970119000004002667', isdp_aid: 'A0000005591010FFFFFFFF8900000100',
|
iccid: '8970119000004002667', isdp_aid: 'A0000005591010FFFFFFFF8900000100',
|
||||||
provider: 'Miranda', name: 'Miranda LTE', class: 'operational',
|
provider: 'Miranda', name: 'Miranda LTE', class: 'operational',
|
||||||
owner: '250-99', icon_type: 'png',
|
owner: '250-99', icon_type: 'png', icon_size: 1234,
|
||||||
});
|
});
|
||||||
assert.deepStrictEqual(rows.map(r => r[0]),
|
assert.deepStrictEqual(rows.map(r => r[0]),
|
||||||
['ICCID', 'ISD-P AID', 'Provider', 'Profile name', 'Class', 'Owner', 'Icon']);
|
['ICCID', 'ISD-P AID', 'Provider', 'Profile name', 'Class', 'Owner', 'Icon']);
|
||||||
assert.strictEqual(rows[0][1], '8970119000004002667');
|
assert.strictEqual(rows[0][1], '8970119000004002667');
|
||||||
|
assert.strictEqual(rows[rows.length - 1][1], 'png · 1234 B');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('esimProfileRows shows the bare icon type when the card sent no image', () => {
|
||||||
|
assert.deepStrictEqual(esimProfileRows({ icon_type: 'jpg' }), [['Icon', 'jpg']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('esimIconDataUrl builds a data URL for png and jpg icons', () => {
|
||||||
|
assert.strictEqual(esimIconDataUrl({ icon_type: 'png', icon: '89504E47' }),
|
||||||
|
'data:image/png;base64,iVBORw==');
|
||||||
|
assert.strictEqual(esimIconDataUrl({ icon_type: 'jpg', icon: 'FFD8' }),
|
||||||
|
'data:image/jpeg;base64,/9g=');
|
||||||
|
assert.strictEqual(esimIconDataUrl({ icon_type: 'png' }), '');
|
||||||
|
assert.strictEqual(esimIconDataUrl({ icon: '89504E47' }), '');
|
||||||
|
assert.strictEqual(esimIconDataUrl(null), '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the profile card renders the icon image next to the rows', () => {
|
||||||
|
const fn = extractFunc(html, 'esimRenderProfiles');
|
||||||
|
assert.match(fn, /esimIconDataUrl\(p\)/);
|
||||||
|
assert.match(fn, /<img src=/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('the eSIM pill is wired into the Phone simulator tab', () => {
|
test('the eSIM pill is wired into the Phone simulator tab', () => {
|
||||||
|
|||||||
@@ -419,6 +419,7 @@ def profiles(app):
|
|||||||
out = []
|
out = []
|
||||||
for p in _repeated(seq, 'profile_info'):
|
for p in _repeated(seq, 'profile_info'):
|
||||||
owner = p.get('profile_owner')
|
owner = p.get('profile_owner')
|
||||||
|
icon = p.get('icon')
|
||||||
out.append({
|
out.append({
|
||||||
'iccid': p.get('iccid'),
|
'iccid': p.get('iccid'),
|
||||||
'isdp_aid': p.get('isdp_aid'),
|
'isdp_aid': p.get('isdp_aid'),
|
||||||
@@ -428,6 +429,8 @@ def profiles(app):
|
|||||||
'name': p.get('profile_name'),
|
'name': p.get('profile_name'),
|
||||||
'class': p.get('profile_class'),
|
'class': p.get('profile_class'),
|
||||||
'icon_type': p.get('icon_type'),
|
'icon_type': p.get('icon_type'),
|
||||||
|
'icon': icon,
|
||||||
|
'icon_size': len(icon) // 2 if icon else None,
|
||||||
'owner': owner.get('profile_owner_plmn') if isinstance(owner, dict) else None,
|
'owner': owner.get('profile_owner_plmn') if isinstance(owner, dict) else None,
|
||||||
})
|
})
|
||||||
return {'profiles': out, 'error': None}
|
return {'profiles': out, 'error': None}
|
||||||
|
|||||||
+13
-3
@@ -12,7 +12,7 @@ from types import SimpleNamespace
|
|||||||
|
|
||||||
from pySim.euicc import (
|
from pySim.euicc import (
|
||||||
AID_ISD_R, CardApplicationISDR, DisableProfileResp, DisableResult,
|
AID_ISD_R, CardApplicationISDR, DisableProfileResp, DisableResult,
|
||||||
EnableProfileResp, EnableResult,
|
EnableProfileResp, EnableResult, Icon, IconType,
|
||||||
Iccid, IsdpAid, ListNotificationResp, NotificationAddress,
|
Iccid, IsdpAid, ListNotificationResp, NotificationAddress,
|
||||||
NotificationMetadata, NotificationMetadataList, ProfileClass, ProfileInfo,
|
NotificationMetadata, NotificationMetadataList, ProfileClass, ProfileInfo,
|
||||||
ProfileInfoListResp, ProfileInfoSeq, ProfileMgmtOperation, ProfileNickname,
|
ProfileInfoListResp, ProfileInfoSeq, ProfileMgmtOperation, ProfileNickname,
|
||||||
@@ -55,7 +55,8 @@ def make_app(profile='Consumer eUICC (SGP.22)', isdr=True):
|
|||||||
return SimpleNamespace(rs=rs), lchan
|
return SimpleNamespace(rs=rs), lchan
|
||||||
|
|
||||||
|
|
||||||
def profile_info(iccid, aid, state=None, nickname=None, cls=None, owner=None):
|
def profile_info(iccid, aid, state=None, nickname=None, cls=None, owner=None,
|
||||||
|
icon=None):
|
||||||
children = [Iccid(decoded=iccid), IsdpAid(decoded=bytes.fromhex(aid))]
|
children = [Iccid(decoded=iccid), IsdpAid(decoded=bytes.fromhex(aid))]
|
||||||
if state:
|
if state:
|
||||||
children.append(ProfileState(decoded=state))
|
children.append(ProfileState(decoded=state))
|
||||||
@@ -65,6 +66,10 @@ def profile_info(iccid, aid, state=None, nickname=None, cls=None, owner=None):
|
|||||||
children.append(ProfileClass(decoded=cls))
|
children.append(ProfileClass(decoded=cls))
|
||||||
if owner:
|
if owner:
|
||||||
children.append(ProfileOwner(children=[ProfileOwnerPLMN(decoded=owner)]))
|
children.append(ProfileOwner(children=[ProfileOwnerPLMN(decoded=owner)]))
|
||||||
|
if icon:
|
||||||
|
icon_type, icon_data = icon
|
||||||
|
children.append(IconType(decoded=icon_type))
|
||||||
|
children.append(Icon(decoded=bytes.fromhex(icon_data)))
|
||||||
return ProfileInfo(children=children)
|
return ProfileInfo(children=children)
|
||||||
|
|
||||||
|
|
||||||
@@ -102,7 +107,7 @@ class EsimTests(unittest.TestCase):
|
|||||||
resp = ProfileInfoListResp(children=[ProfileInfoSeq(children=[
|
resp = ProfileInfoListResp(children=[ProfileInfoSeq(children=[
|
||||||
profile_info('8970119000004002667', 'A0000005591010FFFFFFFF8900000100',
|
profile_info('8970119000004002667', 'A0000005591010FFFFFFFF8900000100',
|
||||||
state='enabled', nickname='Work', cls='operational',
|
state='enabled', nickname='Work', cls='operational',
|
||||||
owner='250-99'),
|
owner='250-99', icon=('png', '89504E470D0A1A0A')),
|
||||||
profile_info('8970119000004002668', 'A0000005591010FFFFFFFF8900000200',
|
profile_info('8970119000004002668', 'A0000005591010FFFFFFFF8900000200',
|
||||||
state='disabled', cls='test'),
|
state='disabled', cls='test'),
|
||||||
])])
|
])])
|
||||||
@@ -117,7 +122,12 @@ class EsimTests(unittest.TestCase):
|
|||||||
self.assertEqual(first['nickname'], 'Work')
|
self.assertEqual(first['nickname'], 'Work')
|
||||||
self.assertEqual(first['class'], 'operational')
|
self.assertEqual(first['class'], 'operational')
|
||||||
self.assertEqual(first['owner'], '250-99')
|
self.assertEqual(first['owner'], '250-99')
|
||||||
|
self.assertEqual(first['icon_type'], 'png')
|
||||||
|
self.assertEqual(first['icon'], '89504E470D0A1A0A')
|
||||||
|
self.assertEqual(first['icon_size'], 8)
|
||||||
self.assertEqual(out['profiles'][1]['state'], 'disabled')
|
self.assertEqual(out['profiles'][1]['state'], 'disabled')
|
||||||
|
self.assertIsNone(out['profiles'][1]['icon'])
|
||||||
|
self.assertIsNone(out['profiles'][1]['icon_size'])
|
||||||
# ISD-R was selected and the previous selection restored
|
# ISD-R was selected and the previous selection restored
|
||||||
self.assertEqual(lchan.selected.name, 'ISD-R')
|
self.assertEqual(lchan.selected.name, 'ISD-R')
|
||||||
self.assertEqual(app.rs.resets, 1)
|
self.assertEqual(app.rs.resets, 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user