@hubbyesim/sdk 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +51 -7
- package/dist/index.cjs +30 -3
- package/dist/index.d.cts +278 -120
- package/dist/index.d.ts +278 -120
- package/dist/index.js +30 -3
- package/llms.txt +16 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.2.0] - 2026-10-17
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `getEsimUsage(iccid, { refresh })` (`GET /v1/esims/{iccid}/usage`): what the traveller can still use on the eSIM, one `DestinationUsage` entry per destination, newest first, with `total_bytes`, `used_bytes`, `remaining_bytes`, `valid_until`, `remaining_seconds`, `package_ids` and `synced_at`. The figures are the ones the Hubby apps show: `total_bytes` is always `used_bytes + remaining_bytes`, and `remaining_seconds` is the time left, set even while `valid_until` is null. Packages for the same destination add up into one entry; data and unlimited packages for one destination are two entries. Packages the traveller bought in the Hubby app count too. `refresh: true` shares `getEsim`'s rate limit. Its response type is `EsimUsageResponse`.
|
|
14
|
+
- The `DestinationUsage` type.
|
|
15
|
+
- The `DestinationSummary` type: the destination object a usage entry, an eSIM package and a bundle carry. Its `type` is the new open response enum `DestinationKind` (`country` or `region`, or any other string).
|
|
16
|
+
- `destination` on `EsimPackage` (`getEsim`'s `packages`): the package's destination, with `id`, `name`, `type`, `iso2` and `iso3`.
|
|
17
|
+
- `name` and `type` on a `Bundle`'s `destination` (`getBundles`, `getDestinationBundles`): the same destination object a usage entry and an eSIM package carry.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- The SDK now speaks Partner API 5.14.0 (`SUPPORTED_API_VERSION`), after 5.13.0.
|
|
22
|
+
- Response enums are open: every enum in a response or resource type (`PackageLifecycleState`, `PackageQueueStatus`, `PackageType`, `BookingSource`, `EndCustomerChannel`, `FairUseThrottleReset`, and the enums inside `Bundle`, `AnalyticsMeta` and the other response types) accepts its known values or any other string, so a value a later Partner API minor adds still type-checks. Editors still complete the known values. Request enums (`CommunicationChannel`, `PartnerTerminateReason`, `SupportedLocale`, query filters) stay closed. Migration: add a `default` branch where you branch on a response enum. An exhaustive `switch` that ends in a `never` check stops compiling until it has one. Nothing changes at runtime.
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
- README and `llms.txt`: a new "Show usage" section: one card per `getEsimUsage` entry; days left come from `remaining_seconds`, with "until <date>" when `valid_until` is set; `remaining_bytes === 0` reads "used up", anything else is the data left; `total_bytes` and `remaining_bytes` are null on unlimited. A destination can appear twice (data and unlimited), and packages bought in the Hubby app count.
|
|
27
|
+
- README and `llms.txt`: "Add a package to a traveller's eSIM" takes the destination from a usage entry and books one of its plans by `bundle_id` (by dimensions under a `package_strategy`). A package's destination is now `destination` on the package, not its booking's item.
|
|
28
|
+
- README: the version line names SDK 2.2.0 and Partner API 5.14.0.
|
|
29
|
+
|
|
9
30
|
## [2.1.0] - 2026-10-17
|
|
10
31
|
|
|
11
32
|
It includes everything prepared for 2.0.1, which was never published.
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ The SDK:
|
|
|
13
13
|
- retries `429` answers and network errors, waiting out `Retry-After`;
|
|
14
14
|
- verifies webhook signatures and timestamps.
|
|
15
15
|
|
|
16
|
-
SDK 2.
|
|
16
|
+
SDK 2.2.0 speaks Partner API **5.14.0** (`SUPPORTED_API_VERSION`). The SDK has its own version number. A new Partner API minor is additive, so this SDK keeps working against it.
|
|
17
17
|
|
|
18
18
|
Requires Node.js 24 or later (or any runtime with `fetch` and Web Crypto: Bun, Deno, edge workers). Use it on your server only. Your API secret must never reach a browser or a mobile app.
|
|
19
19
|
|
|
@@ -158,26 +158,67 @@ An item (in `createBooking`, `getBooking` and `getBookings`) and a package (in `
|
|
|
158
158
|
| `unavailable` | Nothing to issue: cancel the booking or book again. |
|
|
159
159
|
| `redeemed` | Install: the one-tap `install_links` on the phone itself, `lpa` as a QR code for a second device, or `smdp_address` plus `matching_id` for manual entry. |
|
|
160
160
|
| `installed` | Ready: the eSIM is already on the phone. Skip the install screen and say the new package is ready. |
|
|
161
|
-
| `activated` | Usage: the
|
|
161
|
+
| `activated` | Usage: the data left and the days left from `getEsimUsage` (see "Show usage"). |
|
|
162
162
|
| `expired` | Ended: offer a new booking. |
|
|
163
163
|
| `terminated` | The package ended before it attached. After your `terminatePackage`, its item answers `queued` at once. Issue again with a new key (see `idempotency_key_spent` under "Errors"). Or cancel the booking. |
|
|
164
164
|
| `cancelled` | Cancelled. `cancelBooking` cancels every `queued` item of the booking. |
|
|
165
165
|
| a state newer than this SDK | A neutral status. Do not fail. |
|
|
166
166
|
|
|
167
|
+
Every enum in a response (`state`, `status`, `source`, `package_type` and the others) is typed as its known values or any other string, because the Partner API can add a value in a minor release. Handle an unknown value in a `default` branch.
|
|
168
|
+
|
|
167
169
|
An item with no package has no package to read: see `item_has_no_package` under "Errors".
|
|
168
170
|
|
|
171
|
+
### Show usage
|
|
172
|
+
|
|
173
|
+
`getEsimUsage(iccid)` answers what the traveller can still use on the eSIM, one entry per destination, newest first. Show one card per entry. The figures are the ones the Hubby apps show for the same eSIM.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const usage = await hubby.getEsimUsage(iccid);
|
|
177
|
+
|
|
178
|
+
const cards = usage.data.map((entry) => {
|
|
179
|
+
const daysLeft = Math.ceil(entry.remaining_seconds / 86_400);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
title: entry.destination.name,
|
|
183
|
+
data: entry.is_unlimited
|
|
184
|
+
? 'Unlimited'
|
|
185
|
+
: entry.remaining_bytes === 0
|
|
186
|
+
? 'Used up'
|
|
187
|
+
: `${entry.remaining_bytes} of ${entry.total_bytes} bytes left`,
|
|
188
|
+
time:
|
|
189
|
+
entry.valid_until === null
|
|
190
|
+
? `${daysLeft} days left`
|
|
191
|
+
: `${daysLeft} days left, until ${entry.valid_until}`,
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Read each entry as:
|
|
197
|
+
|
|
198
|
+
- Days left: from `remaining_seconds`. It is set even before a date is known, because a package that has not started counts its whole validity.
|
|
199
|
+
- `valid_until` set: add "until <date>". Null means the time that counts has not started yet. It does not mean the entry is unused: an older package in it can already be in use.
|
|
200
|
+
- `remaining_bytes === 0`: used up, even when time is still left. A top-up adds to it.
|
|
201
|
+
- Anything else: the data left, `remaining_bytes` of `total_bytes`.
|
|
202
|
+
- `is_unlimited`: `total_bytes` and `remaining_bytes` are null; `used_bytes` is still set.
|
|
203
|
+
|
|
204
|
+
Every package for the same destination adds up into one entry, so two Spain packages are one Spain card. A destination can appear twice: one entry for its data packages and one for its unlimited packages. A country and a region that covers it are two entries. Packages the traveller bought in the Hubby app count too. `package_ids` names the packages an entry adds up, the same `id`s as in `getEsim`'s `packages`. An empty `data` means nothing on the eSIM can still be used.
|
|
205
|
+
|
|
206
|
+
`total_bytes` counts the whole allowance of every package in the entry, ended packages included, and is always `used_bytes + remaining_bytes`: data an ended package did not use counts as used. Draw a gauge from `remaining_bytes` against `total_bytes`. `synced_at` is when the figures were last read from the network; `refresh: true` reads them now, under the same rate limit as `getEsim`.
|
|
207
|
+
|
|
169
208
|
### Add a package to a traveller's eSIM
|
|
170
209
|
|
|
171
210
|
Each traveller has one Universal eSIM, and every package you issue for them attaches to it. To add a package, book again for the same `external_user_id`, then issue. There is no `topUp()` call; this is it.
|
|
172
211
|
|
|
212
|
+
To top up a usage entry, take the destination from the entry: list its plans and book one by `bundle_id`.
|
|
213
|
+
|
|
173
214
|
```ts
|
|
215
|
+
// entry: the usage entry the traveller tops up (see "Show usage").
|
|
216
|
+
const plans = await hubby.getDestinationBundles(entry.destination.id);
|
|
174
217
|
const topUp = await hubby.createBooking({
|
|
175
218
|
external_reference: 'PARTNER-LOC-5120',
|
|
176
219
|
package_specifications: [
|
|
177
220
|
{
|
|
178
|
-
|
|
179
|
-
package_type: 'data-limited',
|
|
180
|
-
size: '3GB',
|
|
221
|
+
bundle_id: plans.data[0].id, // the plan the traveller picks
|
|
181
222
|
external_user_id: 'partner-user-88213', // the same traveller
|
|
182
223
|
},
|
|
183
224
|
],
|
|
@@ -190,7 +231,9 @@ const added = await hubby.issuePackage('PARTNER-LOC-5120', topUp.data.items![0].
|
|
|
190
231
|
// added.data.state is 'installed' once the eSIM is on the phone: skip the install screen.
|
|
191
232
|
```
|
|
192
233
|
|
|
193
|
-
|
|
234
|
+
When your Partner has a `package_strategy` (no `bundle_id`), book by dimensions instead: `destination: entry.destination.iso2 ?? entry.destination.name` (a region has no `iso2`).
|
|
235
|
+
|
|
236
|
+
The Hubby apps offer a top-up only for a destination the traveller already holds; any other destination is a purchase from the shop. The Partner API does not enforce this, so your app must. Each package in `getEsim`'s `packages` carries its `destination` (`id`, `name`, `type`, `iso2`, `iso3`), the same object a usage entry and a catalogue bundle carry.
|
|
194
237
|
|
|
195
238
|
### Every call
|
|
196
239
|
|
|
@@ -209,6 +252,7 @@ The Hubby apps offer a top-up only for a destination the traveller already holds
|
|
|
209
252
|
| `getPackage(booking, item)` | `GET /v1/bookings/{booking}/items/{item}/package` | the package |
|
|
210
253
|
| `terminatePackage(booking, item, input?)` | `DELETE /v1/bookings/{booking}/items/{item}/package` | nothing (`undefined`): the API answers `204` |
|
|
211
254
|
| `getEsim(iccid, input?)`, `getEsimInstructions(iccid, input?)` | `GET /v1/esims/{iccid}…` | the eSIM and its packages, or its install instructions |
|
|
255
|
+
| `getEsimUsage(iccid, input?)` | `GET /v1/esims/{iccid}/usage` | what the traveller can still use, one `DestinationUsage` per destination (see "Show usage") |
|
|
212
256
|
| `getCompatibleDevices()`, `checkDeviceCompatibility(input)` | `/v1/device-compatibility` | the devices that take an eSIM, or the answer for one |
|
|
213
257
|
| `createRedirectToken(input)` | `POST /v1/redirect-tokens` | a redirect token |
|
|
214
258
|
| `getAnalyticsSummary(query?)`, `getAnalyticsFunnel(query?)`, `getAnalyticsTimeseries(query?)`, `getAnalyticsBreakdowns(query?)` | `GET /v1/analytics/…` | the analytics for a date range |
|
|
@@ -223,7 +267,7 @@ The Hubby apps offer a top-up only for a destination the traveller already holds
|
|
|
223
267
|
|
|
224
268
|
`Booking.is_cancellable` tells you before you call.
|
|
225
269
|
|
|
226
|
-
`getEsim(iccid, { refresh: true })`
|
|
270
|
+
`getEsim(iccid, { refresh: true })` and `getEsimUsage(iccid, { refresh: true })` read the network now, and are rate-limited per Partner, per minute, together. A refused refresh answers `429` with `Retry-After`, which the SDK waits out (see "What is retried"). A read without `refresh` is not limited and may be polled.
|
|
227
271
|
|
|
228
272
|
Every input and response has a type you can import by name:
|
|
229
273
|
|
package/dist/index.cjs
CHANGED
|
@@ -1045,6 +1045,15 @@ var partnerApiV1EsimsShow = (options) => (options.client ?? client).get({
|
|
|
1045
1045
|
url: "/v1/esims/{iccid}",
|
|
1046
1046
|
...options
|
|
1047
1047
|
});
|
|
1048
|
+
var partnerApiV1EsimsUsage = (options) => (options.client ?? client).get({
|
|
1049
|
+
security: [
|
|
1050
|
+
{ name: "X-Api-Key", type: "apiKey" },
|
|
1051
|
+
{ name: "X-Signature", type: "apiKey" },
|
|
1052
|
+
{ name: "X-Timestamp", type: "apiKey" }
|
|
1053
|
+
],
|
|
1054
|
+
url: "/v1/esims/{iccid}/usage",
|
|
1055
|
+
...options
|
|
1056
|
+
});
|
|
1048
1057
|
var partnerApiV1EsimsInstructions = (options) => (options.client ?? client).get({
|
|
1049
1058
|
security: [
|
|
1050
1059
|
{ name: "X-Api-Key", type: "apiKey" },
|
|
@@ -1154,8 +1163,8 @@ async function signRequest(secret, request) {
|
|
|
1154
1163
|
}
|
|
1155
1164
|
|
|
1156
1165
|
// lib/version.ts
|
|
1157
|
-
var SUPPORTED_API_VERSION = "5.
|
|
1158
|
-
var SDK_VERSION = "2.
|
|
1166
|
+
var SUPPORTED_API_VERSION = "5.14.0";
|
|
1167
|
+
var SDK_VERSION = "2.2.0";
|
|
1159
1168
|
|
|
1160
1169
|
// lib/transport.ts
|
|
1161
1170
|
var DEFAULT_RETRY = {
|
|
@@ -1401,7 +1410,22 @@ var Hubby = class {
|
|
|
1401
1410
|
client: this.#client,
|
|
1402
1411
|
...options,
|
|
1403
1412
|
path: { iccid },
|
|
1404
|
-
query: input
|
|
1413
|
+
query: refreshQuery(input)
|
|
1414
|
+
})
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* `GET /v1/esims/{iccid}/usage`: what the traveller can still use on the
|
|
1419
|
+
* eSIM, one entry per destination, newest first. `refresh: true` reads
|
|
1420
|
+
* the network now; it shares `getEsim`'s per-Partner rate limit.
|
|
1421
|
+
*/
|
|
1422
|
+
async getEsimUsage(iccid, input = {}, options = {}) {
|
|
1423
|
+
return unwrap(
|
|
1424
|
+
await partnerApiV1EsimsUsage({
|
|
1425
|
+
client: this.#client,
|
|
1426
|
+
...options,
|
|
1427
|
+
path: { iccid },
|
|
1428
|
+
query: refreshQuery(input)
|
|
1405
1429
|
})
|
|
1406
1430
|
);
|
|
1407
1431
|
}
|
|
@@ -1583,6 +1607,9 @@ function unwrap(result) {
|
|
|
1583
1607
|
}
|
|
1584
1608
|
return result.data;
|
|
1585
1609
|
}
|
|
1610
|
+
function refreshQuery(input) {
|
|
1611
|
+
return input.refresh === void 0 ? void 0 : { refresh: `${input.refresh}` };
|
|
1612
|
+
}
|
|
1586
1613
|
|
|
1587
1614
|
// lib/webhooks.ts
|
|
1588
1615
|
var WebhookVerificationError = class extends Error {
|