• Joined on 2025-08-13

@dariusz/si-api (0.1.0)

Published 2026-06-16 13:47:32 +02:00 by dariusz

Installation

@dariusz:registry=
npm install @dariusz/si-api@0.1.0
"@dariusz/si-api": "0.1.0"

About this package

si-api

Klient Node.js do API portalu Światłowód Inwestycje (portal.swiatlowodinwestycje.pl).

Moduł obsługuje logowanie i autoryzowane żądania do dwóch backendów:

  • Portalhttps://portal.swiatlowodinwestycje.pl/portal-si-be
  • Bramka APIhttps://apigateway-prd.swiatlowodinwestycje.pl/si/rest-api

Wymaga Node ≥ 18 (używa wbudowanego fetch). Brak zależności zewnętrznych. Moduł w formacie ESM.

Dokumentacja jest aktualizowana na bieżąco wraz z rozwojem modułu.


Jak działa logowanie

Ustalone na podstawie analizy ruchu (HAR portal.swiatlowodinwestycje.pl.har).

  1. Żądanie logowania:

    POST {PORTAL_BASE}/loginservices/users/login
    Content-Type: application/json;charset=UTF-8
    body: {"username": "...", "password": "..."}
    
  2. Odpowiedź (HTTP 200):

    {
      "id": 305930,
      "username": "uzytkownik@firma.pl",
      "token": "TMj0e9nJon8yjB7ZCAh2PUREI0aEP7",
      "ukeId": "8465",
      "oaName": "PLAST_COM_SC",
      "accountStatus": 0,
      "oaId": 305922,
      "generateCert": 0
    }
    
  3. Autoryzacja kolejnych żądań: zwrócony token wysyłany jest w nagłówku token: <wartość>.

    • Działa zarówno dla portalu (portal-si-be), jak i dla bramki (apigateway-prd .../si/rest-api).
    • Nie ma OAuth/Bearer — to prosty token w niestandardowym nagłówku token.

Instalacja / uruchomienie

Projekt nie ma zależności — wystarczy Node ≥ 18.

Z prywatnego rejestru npm (Gitea)

Paczka jest publikowana w prywatnym rejestrze Gitea jako si-api. U konsumenta utwórz .npmrc (nie commituj go) i zainstaluj:

registry=https://gitea.naurecki.pl/api/packages/dariusz/npm/
//gitea.naurecki.pl/api/packages/dariusz/npm/:_authToken=TWOJ_TOKEN
npm install si-api

Uwaga: nazwa jest bez scope (si-api, nie @dariusz/si-api), ponieważ Apache przed Gitea ma AllowEncodedSlashes Off i odrzuca %2F w ścieżce scoped paczek. Aby używać nazwy scoped, na serwerze Gitea dodaj do vhosta Apache AllowEncodedSlashes NoDecode + nocanon przy ProxyPass i przeładuj Apache.

Publikacja nowej wersji (z katalogu projektu, mając .npmrc z tokenem):

npm version patch   # podbij wersję
npm publish

Test logowania z linii poleceń

node bin/login.js <username> <password>
# lub przez zmienne środowiskowe:
SI_USERNAME=... SI_PASSWORD=... node bin/login.js

Użycie programistyczne

import { SiApiClient } from "./src/index.js";

const client = new SiApiClient();
await client.login("uzytkownik@firma.pl", "haslo");

// skróty do zbadanych endpointów:
const roles = await client.getUserRoles();
const orgs = await client.getOrganizations();

// generyczne żądanie do portalu:
const mapConf = await client.request("/extras/mapConf");

// generyczne żądanie do bramki API:
const spec = await client.request(
  "/productCatalogManagement/v1/productSpecification",
  { base: "gateway", query: { fields: "id,name,productSpecificationType" } },
);

Konstruktor

new SiApiClient({ portalBase?, gatewayBase?, token? })
  • portalBase — domyślnie https://portal.swiatlowodinwestycje.pl/portal-si-be
  • gatewayBase — domyślnie https://apigateway-prd.swiatlowodinwestycje.pl/si/rest-api
  • token — można podać istniejący token i pominąć login()

Metody

Metoda Opis
login(username, password) Loguje, zapisuje token i session, zwraca obiekt sesji
request(path, opts) Autoryzowane żądanie. opts: base ("portal"|"gateway"), method, query, body, headers
getUserRoles() Role zalogowanego użytkownika (/services/users/roles/{id})
getOrganizations() Lista organizacji (/services/organizations)
getProductSpecifications(opts) Katalog typów usług (TMF productSpecification)
getProductOfferings(opts) Oferty produktowe (TMF productOffering)
searchProducts(opts) Inwentarz usług klienta (TMF product) — obsługuje formularz wyszukiwania
getProduct(id, opts) Szczegóły pojedynczej usługi (TMF product/{id})
startServiceTest(opts) Inicjuje telediagnostykę (pomiar) — tworzy zlecenie i id pomiaru
listServiceTests(opts) Historia pomiarów (ekran „Zamówienia i zgłoszenia")
getServiceTest(testId) Surowy ServiceTest po id (z testMeasure[])
getServiceTestResult(testId) Wynik uporządkowany: meta + metryki + werdykty

Właściwości / błędy

  • client.token, client.session, client.isAuthenticated
  • SiApiError — rzucany przy błędach; pola .status i .body

Zbadane endpointy (z HAR)

Portal (portal-si-be):

  • POST /loginservices/users/login
  • GET /services/users/roles/{userId}
  • GET /services/organizations
  • GET /services/api
  • GET /extras/mapConf

Bramka (apigateway-prd .../si/rest-api), wszystkie wymagają nagłówka token:

metoda ścieżka wymagany Accept uwagi
GET /productCatalogManagement/v1/productSpecification katalog typów usług
GET /productCatalogManagement/v1/productOffering oferty
GET /productInventoryManagement/v1/product inwentarz (lista, filtry)
GET /productInventoryManagement/v1/product/{id} application/hal+json (zalecane) szczegóły usługi
POST /serviceTestManagement/v1/serviceTest application/json inicjalizacja pomiaru — inny Accept406
GET /serviceTestManagement/v1/serviceTest historia pomiarów (lista)
GET /serviceTestManagement/v1/serviceTest/{id} application/hal+json wynik pomiaru — bez tego ⇒ 502

Negocjacja Accept jest tu nietypowo restrykcyjna. Bramka nie akceptuje */* na końcówkach serviceTest: POST wymaga dokładnie application/json (inaczej 406 code=62 "Not applicable"), a GET szczegółów wymaga dokładnie application/hal+json (inaczej 502). Metody klienta ustawiają właściwe nagłówki automatycznie.


Katalog usług (TM Forum API)

Bramka udostępnia API w standardzie TM Forum (TMF). Katalog składa się z trzech warstw:

1. productSpecification — typy usług (katalog)

GET /productCatalogManagement/v1/productSpecification

Zwraca listę specyfikacji (typów usług). W środowisku produkcyjnym dostępne są m.in.:

id productSpecificationType name
DATA2 VLAN_BROADBAND DATA2 - VLAN N:1 w klasie C3
SLA ADDON Usługa SLA na usuwanie uszkodzeń
ACCESS_TERMINAL DEVICE ONT
POE_INJECTOR DEVICE Power Injector
ADDITIONALTASK ADDITIONALTASK Czynność dodatkowa
ACCESS PRODUCT Łącze dostępowe

Z parametrem fields=...,productSpecCharacteristic każda specyfikacja zawiera charakterystyki — to one definiują pola/opcje formularza. Dla DATA2 (VLAN_BROADBAND, BSA_N:1):

  • serviceOption (Opcja prędkości, słownik): 300M/50M, 600M/100M, 1G/300M, 2G/600M
  • classOfService (Klasa usługi): C3
  • pduType (Rodzaj punktu styku): Ethernet
  • idPdu, vlan2pdu, vlan, customerVlan, remoteId, aoBundleId, freezePeriodStart/End — pola techniczne

Każda charakterystyka ma m.in.: name, description, valueType (Dictionary/String/Number), isRequired, visible, oraz productSpecCharacteristicValue z dozwolonymi wartościami (value, isDefault, allowedTechnology, np. FTTH).

const specs = await client.getProductSpecifications();
const data2 = await client.getProductSpecifications({
  type: "VLAN_BROADBAND",
  businessServiceType: "BSA_N:1",
  withCharacteristics: true,
});

2. productOffering — oferty

GET /productCatalogManagement/v1/productOffering

Zwraca oferty powiązane ze specyfikacją, np. DATA2_OFFER w kategorii S-I ("Oferty S-I").

const offers = await client.getProductOfferings({
  specificationType: "VLAN_BROADBAND",
  businessServiceType: "BSA_N:1",
});

3. productInventoryManagement/v1/product — inwentarz (formularz wyszukiwania)

GET /productInventoryManagement/v1/product

Zwraca faktyczne usługi klienta. To zapytanie obsługuje formularz wyszukiwania w portalu. Parametry filtrowania (kropkowa notacja TMF):

parametr przykład opis
productSpecification.id DATA2 typ usługi
productSpecification.productSpecificationType VLAN_BROADBAND kategoria specyfikacji
businessServiceType BSA_N:1 typ usługi biznesowej
characteristic.name serviceOption nazwa charakterystyki
characteristic.value 2G/600M wartość charakterystyki
productRelationship.product.linkId 026595759023 id łącza
status active status usługi
offset, limit 0, 10 paginacja
fields lista pól które pola zwrócić

Przykładowy rekord usługi:

{
  "id": "300126169065",
  "characteristic": [{ "name": "serviceOption", "value": "2G/600M" }],
  "productRelationship": [{ "product": { "linkId": "026595759023", "addressName": "84-103 ŁEBCZ UL. WICHROWA 17/" } }],
  "productSpecification": { "id": "DATA2", "productSpecificationType": "VLAN_BROADBAND" },
  "relatedParty": [{ "id": "8465", "name": "PLASTCOM ... Sp. Jawna", "role": "owner" }],
  "startDate": "2026-06-08T20:49:58+02:00",
  "status": "active"
}
const products = await client.searchProducts({
  specificationId: "DATA2",
  specificationType: "VLAN_BROADBAND",
  businessServiceType: "BSA_N:1",
  characteristicName: "serviceOption",
  offset: 0,
  limit: 10,
});

Szczegóły usługi — product/{id}

GET /productInventoryManagement/v1/product/{id}

Portal wysyła nagłówki accept: application/hal+json oraz x_client_assent: TRUE (metoda getProduct ustawia je domyślnie, ale nie są wymagane — sprawdzone).

Odpowiedź jest bogatsza niż rekord z listy — dochodzą:

pole przykład opis
isBundle false czy usługa jest pakietem
isCustomerVisible true widoczność dla klienta
productOffering { id: "DATA2_OFFER", name: "oferta DATA2" } powiązana oferta
productOrderItem[] orderId: "3988380887", orderItemAction: "add" referencja do zamówienia (orderHref → productOrderManagement)
productRelationship[].type RELIES_ON typ relacji do produktu łącza
productRelationship[].product.id 300126169064 produkt łącza (osobny byt, href do jego szczegółów)
productSpecification.name/version DATA2 - VLAN N:1 w klasie C3, 1 nazwa + wersja specyfikacji

Uwaga: usługa (300126169065) RELIES_ON produkt łącza (300126169064) — to dwa powiązane produkty TMF. linkId (026595759023) jest wspólnym identyfikatorem łącza. orderHref wskazuje na inny host backendu: https://isi-cc.tp.pl/si/rest-api/....

const detail = await client.getProduct("300126169065");

Identyfikacja usług

Każda usługa (Product) jest identyfikowana wieloma kluczami:

pole przykład znaczenie
id 300126169065 główny identyfikator usługi (TMF Product id)
productRelationship[].product.linkId 026595759023 id łącza dostępowego (fizyczne łącze)
characteristic customerVlan 35 VLAN w sieci OA (klienta)
characteristic vlan 2230 VLAN na OLT
characteristic vlan2pdu 1201 VLAN na punkcie styku
characteristic idPdu GDA_SI_PLASTCOM_A identyfikator punktu styku
relatedParty[].id 8465 właściciel = ukeId operatora (z sesji logowania)

Stan na 2026-06-16 (konto PLAST_COM_SC, ukeId 8465): 1 aktywna usługa — DATA2 VLAN N:1, opcja 2G/600M, pod adresem 84-103 Łebcz, ul. Wichrowa 17. Pozostałe typy specyfikacji (SLA, ONT, Power Injector, łącze ACCESS) nie mają pozycji w inwentarzu.


Telediagnostyka (serviceTestManagement)

Inicjalizacja pomiaru to pojedynczy POST, który tworzy zlecenie pomiaru (state: "inprogress"). Pomiar trwa zwykle ~60–90 s, po czym state zmienia się na completed, a wynik (zbiór metryk) odczytuje się osobnym GET po id. Wynik nie jest plikiem binarnym — to JSON (patrz „Pobieranie wyników").

Przepływ w portalu (z HAR)

Przed pokazaniem formularza portal doczytuje produkty na łączu:

  • GET /product?id=300126169064ACCESS (łącze, linkId 026595759023)
  • GET /product?id=300126169065,300126169066DATA2 (usługa) + ACCESS_TERMINAL (ONT)

Następnie wysyła zlecenie:

POST /serviceTestManagement/v1/serviceTest
Accept: application/json          ← WYMAGANE (inny Accept, w tym */*, ⇒ 406 "Not applicable")
Content-Type: application/json;charset=UTF-8
token: <token z logowania>

Body:

{
  "@type": "WHServiceTestV2",
  "@baseType": "ServiceTest",
  "name": "Pomiar z Portala",
  "mode": "ONDEMAND",
  "testSpecification": { "@referredType": "ServiceTestSpecification", "id": "TEST_COMPLEX" },
  "relatedParty": [{ "@referredType": "Organization", "id": "8465", "role": "owner", "name": "PLAST_COM_SC" }],
  "relatedProduct": { "@referredType": "Product", "id": "300126169065" }
}
  • relatedParty.id = ukeId, relatedParty.name = oaName — oba z sesji logowania.
  • relatedProduct.id = id usługi (np. DATA2).

Odpowiedź 201:

{
  "id": "000000002135910",
  "href": "https://portal.swiatlowodinwestycje.pl/si/rest-api/necmplusrest/rest-api/serviceTestManagement/v1/serviceTest/000000002135910",
  "name": "Pomiar z Portala",
  "mode": "ONDEMAND",
  "state": "inprogress",
  "startDateTime": "2026-06-16T10:11:31Z",
  "testSpecification": { "id": "TEST_COMPLEX" },
  "relatedProduct": { "id": "300126169065" },
  "relatedParty": [{ "id": "8465", "name": "PLAST_COM_SC", "role": "owner" }]
}

id to „ID pomiaru" widoczne w portalu. state startuje jako inprogress; status/wynik można odpytywać przez href (= getServiceTest(id)).

Scenariusze testu (testSpecification.id)

scenariusz opis (z formularza)
CHECK_IMPACT Sprawdzenie wystąpienia prac planowych i awarii masowych dla usługi
GET_TRAFFIC Odczyt ruchu na porcie online
TEST_COMPLEX Pomiar wszystkich usług na łączu
TEST_GPON Telediagnostyka dla usług w technologii FTTH

Stałe dostępne jako TEST_SCENARIOS.

import { SiApiClient, TEST_SCENARIOS } from "./src/index.js";

const client = new SiApiClient();
await client.login("uzytkownik@firma.pl", "haslo");

const test = await client.startServiceTest({
  productId: "300126169065",
  scenario: TEST_SCENARIOS.TEST_COMPLEX,
});
console.log("ID pomiaru:", test.id, "stan:", test.state);

// później: status/wynik
const status = await client.getServiceTest(test.id);

⚠️ startServiceTest rejestruje realne zlecenie pomiaru w systemie SI (tak jak kliknięcie „Wykonaj telediagnostykę" w portalu).

Pobieranie wyników („plików") diagnostyki

Ważne: wynik diagnostyki nie jest osobnym plikiem binarnym. Komunikat w portalu („Wynik pomiaru będzie dostępny wkrótce w pliku") odnosi się do raportu, który portal renderuje po stronie klienta z danych JSON. Backend zwraca wynik jako tablicę testMeasure[] w szczegółach pomiaru. „Pobranie pliku" = pobranie tego JSON-a (GET po id) i ewentualny zapis/eksport po naszej stronie.

1. Historia pomiarów (lista):

GET /serviceTestManagement/v1/serviceTest
    ?testSpecification.id=TEST_GPON,GET_TRAFFIC,TEST_COMPLEX,CHECK_IMPACT
    &fields=id,startDateTime,relatedProduct,testSpecification,state
    &offset=0&limit=10&sort=-id

Zwraca listę pomiarów (najnowsze pierwsze): id, state, testSpecification.id, startDateTime.

2. Wynik pomiaru (szczegóły):

GET /serviceTestManagement/v1/serviceTest/{id}
Accept: application/hal+json          ← WYMAGANE (bez tego backend zwraca 502)

Po zakończeniu state: "completed", jest endDateTime i testMeasure[] (dla TEST_COMPLEX ~131 metryk).

Każda metryka (testMeasure[]):

pole przykład
metricName COLLECT_DATA.Dane.data.OLT_OntRxPower
metricDescription Pomiar wszystkich usług na łączu
value -16.946 dBm
criticity OK / NOK / NONE
interpretation [COMPLEX-02] Usługa działa niepoprawnie. (gdy istotne)
captureDateTime 2026-06-16T10:11:31Z

Uwaga: testMeasure[] bywa zakończone elementem null — należy filtrować (getServiceTestResult robi to automatycznie). Werdykty to metryki z criticityNONE.

// historia
const history = await client.listServiceTests({ limit: 10 });

// wynik konkretnego pomiaru (uporządkowany)
const result = await client.getServiceTestResult("000000002135910");
console.log(result.state, result.scenario, result.measures.length);
for (const v of result.verdicts) {
  console.log(`[${v.criticity}] ${v.metricName}: ${v.interpretation ?? v.value}`);
}

// zapis surowego wyniku do pliku JSON (po naszej stronie)
import { writeFile } from "node:fs/promises";
const raw = await client.getServiceTest("000000002135910");
await writeFile("pomiar-000000002135910.json", JSON.stringify(raw, null, 2));

Struktura metricName

metricName jest hierarchiczny, segmenty rozdzielone kropką:

<SCENARIUSZ>.<Sekcja czytelna>.<ścieżka.techniczna...>.<pole>
  • pierwszy segment = grupa/źródło danych (CHECK_IMPACT, GET_TRAFFIC, COLLECT_DATA, TEST_GPON, TEST_DATA_OUT, TEST_PPP2, TEST_COMPLEX_OUT, …),
  • drugi segment = czytelna nazwa sekcji (np. Dane, Lokalizacja, VLAN, Awarie globalne, Uszkodzenie grupowe - brak sygnału),
  • ostatni segment = nazwa pola (np. OLT_OntRxPower, rack, upFwdBytes).
  • Werdykt główny to jedyna metryka, której metricName jest równy nazwie scenariusza (np. metricName == "GET_TRAFFIC"); ma criticity (OK/NOK) i interpretation.

getServiceTestResult zwraca to spłaszczone: { id, state, scenario, startDateTime, endDateTime, productId, measures[], verdicts[] }, gdzie verdicts = metryki z criticity ≠ NONE.

Format wyniku per scenariusz

Zweryfikowane na realnych pomiarach (usługa DATA2 300126169065, łącze 026595759023):

scenariusz metryk główne grupy (prefiks metricName) werdykt ([kod])
CHECK_IMPACT ~5 CHECK_IMPACT (awarie globalne, podejrzenia, parametry: eventCode) [EAI-001] brak awarii/prac planowych
GET_TRAFFIC ~13 GET_TRAFFIC (Lokalizacja: rack/shelf/slot/port/ontId; VLAN: cVlan/sVlan; liczniki: up/dnFwdFrames/Bytes) [TRAFF-…] jest/brak ruchu
TEST_GPON ~103 TEST_GPON (werdykt) + COLLECT_DATA (~100 pól OLT/ONT) [GPONONT-…] łącze i GPON OK
TEST_COMPLEX ~131 COLLECT_DATA (~102), GET_TRAFFIC (~13), CHECK_IMPACT (5), TEST_GPON (1), TEST_DATA_OUT, TEST_PPP2, TEST_COMPLEX_OUT [COMPLEX-…] zbiorczy

TEST_COMPLEX jest złożony — agreguje metryki pozostałych scenariuszy (każdy pod własnym prefiksem). TEST_PPP2 zwraca GI-100 Usługa nie może być mierzona danym scenariuszem pomiarowym, gdy nie dotyczy danej usługi.

Sekcja COLLECT_DATA (TEST_GPON / TEST_COMPLEX)

Najbogatsza sekcja — surowe dane z OLT/ONT. Wybrane pola (COLLECT_DATA.Dane.data.*):

pole przykład znaczenie
OLT_OltName GD_LEBCZ_GPUCKA_A nazwa OLT
OLT_OltDesc … NOKIA ISAM model/opis OLT
OLT_OntSerialNumber ZTEGD63CE810 nr seryjny ONT
OLT_UserMacAddress 48:8F:5A:25:D7:EC adres MAC urządzenia klienta (pusty FAILED - nullSubTree, gdy brak świeżego ruchu)
OLT_OntRxPower / OLT_OltRxPower -16.946 dBm / -20.4 dBm moc odbierana ONT/OLT
OLT_OltTxPower / OLT_OntTxPower 5.5 dBm / 2.472 dBm moc nadawana
OLT_OntDistance 1.7 km dystans ONT od OLT
OLT_OntOperStatusDesc Up stan operacyjny ONT
OLT_OntCurrAlarmsDesc No Defects bieżące alarmy ONT
OLT_OntCVLAN / OLT_OntSVLAN 35 / 2230 VLAN klienta / VLAN usługi
OLT_CustomerId 300126169065 id usługi (= Product id)
VLANCur1DayUpBytes / …DnBytes 35: 1244300 wolumen ruchu (bieżąca doba), per VLAN
VLANPrev1DayUpBytes / …DnBytes 35: 5853355226 wolumen ruchu (poprzednia doba)

Adres MAC pojawia się tylko w TEST_GPON i TEST_COMPLEX (przez COLLECT_DATA), i tylko gdy w oknie pomiaru był ruch. CHECK_IMPACT i GET_TRAFFIC nie zawierają MAC.


Narzędzia CLI (bin/)

skrypt działanie
node bin/login.js [user] [pass] test logowania (lub ENV SI_USERNAME/SI_PASSWORD)
node bin/run-all-tests.js [productId] inicjuje 4 scenariusze, czeka na ukończenie, zapisuje results/<scenario>-<id>.json
node bin/render-html.js generuje wizualizacje HTML z results/*.jsonresults/html/ (raport per pomiar + index.html)

run-all-tests.js używa domyślnie productId=300126169065 i danych z ENV (fallback do zaszytych). Polling co 5 s, timeout 5 min; toleruje przejściowe 502 podczas liczenia.


Struktura projektu

si-api/
├── README.md              # ta dokumentacja (aktualizowana na bieżąco)
├── package.json
├── src/
│   ├── index.js           # eksporty publiczne (SiApiClient, SiApiError, TEST_SCENARIOS, *_BASE)
│   └── client.js          # SiApiClient, SiApiError, stałe baz URL i scenariuszy
├── bin/
│   ├── login.js           # CLI do testu logowania
│   ├── run-all-tests.js   # inicjacja 4 scenariuszy + polling + zapis JSON
│   └── render-html.js     # generator wizualizacji HTML z wyników
└── results/               # wyniki pomiarów (generowane)
    ├── <scenario>-<id>.json
    └── html/              # raporty HTML + index.html
Details
npm
2026-06-16 13:47:32 +02:00
2
UNLICENSED
23 KiB
Assets (1)
Versions (3) View all
0.3.0 2026-06-17
0.2.0 2026-06-16
0.1.0 2026-06-16