@fleetless/sdk 3.0.2 → 3.0.3
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 +8 -0
- package/CONTRIBUTING.md +76 -71
- package/README.md +64 -344
- package/SECURITY.md +11 -11
- package/dist/index.cjs +17 -18
- package/dist/index.d.cts +80 -93
- package/dist/index.d.ts +80 -93
- package/dist/index.js +17 -18
- package/package.json +8 -4
- package/RELEASING.md +0 -195
package/dist/index.d.ts
CHANGED
|
@@ -607,13 +607,13 @@ type ErrorCode = (typeof ERROR_CODES)[number];
|
|
|
607
607
|
* later on the same socket — nobody knows — but the caller cannot be made
|
|
608
608
|
* to wait forever for that.
|
|
609
609
|
* - `command_outcome_unknown`: worse than a timeout, and told apart from it
|
|
610
|
-
* on purpose — the
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
610
|
+
* on purpose — the connection that carried the command was replaced by a
|
|
611
|
+
* new one (a reconnect) before any reply arrived. The server, if it
|
|
612
|
+
* answered at all, answered a socket that no longer exists, so the
|
|
613
|
+
* command may or may not have run. Never retried automatically — that
|
|
614
|
+
* could run an action twice — the caller recovers by reading the job
|
|
615
|
+
* (e.g. `actions.subscribe`), since state is observed by slug regardless
|
|
616
|
+
* of which connection asked for it.
|
|
617
617
|
* - `unexpected_response`: the server answered `ok:true` but left out
|
|
618
618
|
* something the command is defined to always return (e.g. no `job` on a
|
|
619
619
|
* successful `invoke`) — a contract violation the SDK noticed, not a
|
|
@@ -801,20 +801,19 @@ interface JobSubscriptionHandlers {
|
|
|
801
801
|
interface JobSubscription {
|
|
802
802
|
/**
|
|
803
803
|
* Stops this subscription. The `unsubscribe` frame reaches the server only
|
|
804
|
-
* when this was the **last** holder of the robot
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
* `
|
|
808
|
-
*
|
|
809
|
-
* than once.
|
|
804
|
+
* when this was the **last** holder of the robot/slug pair and the channel
|
|
805
|
+
* is connected — subscriptions are reference-counted across kinds, so a
|
|
806
|
+
* second `actions.subscribe`, `datapoints.subscribe` or in-flight
|
|
807
|
+
* `services.call` on the same pair keeps its stream running. Safe to call
|
|
808
|
+
* more than once.
|
|
810
809
|
*/
|
|
811
810
|
unsubscribe(): void;
|
|
812
811
|
}
|
|
813
812
|
|
|
814
813
|
/**
|
|
815
814
|
* Long-running work on a robot, reachable as `client.actions`. An action is
|
|
816
|
-
* a ROS action the developer exposed under a slug: it is invoked,
|
|
817
|
-
*
|
|
815
|
+
* a ROS action the developer exposed under a slug: it is invoked, runs as
|
|
816
|
+
* long as it runs, and reports back while it does.
|
|
818
817
|
*/
|
|
819
818
|
interface ActionsApi {
|
|
820
819
|
/**
|
|
@@ -822,7 +821,7 @@ interface ActionsApi {
|
|
|
822
821
|
* — the job id is informative, not the result. Feedback, progress and the
|
|
823
822
|
* eventual result arrive separately over `subscribe`. A second invoke of
|
|
824
823
|
* the same slug while one is already running is refused `busy`, with
|
|
825
|
-
* `error.details.running` naming the job
|
|
824
|
+
* `error.details.running` naming the running job.
|
|
826
825
|
*
|
|
827
826
|
* `options.patienceMs` bounds goal *acceptance* only — once a goal
|
|
828
827
|
* is accepted this call has already resolved; the job then runs as long
|
|
@@ -1106,7 +1105,7 @@ interface AssetsApi {
|
|
|
1106
1105
|
* **Do not also install `createMeshLoader` on the same manager.** The two
|
|
1107
1106
|
* consume different URDF sources — this method fetches the URDF's *raw*
|
|
1108
1107
|
* bytes, `createMeshLoader` is meant to pair with `urdf()`'s
|
|
1109
|
-
* cloud-rewritten text. **
|
|
1108
|
+
* cloud-rewritten text. **Not a double-fetch.**
|
|
1110
1109
|
* What actually happens is asymmetric breakage,
|
|
1111
1110
|
* whichever URDF text the combination ends up parsing: paired with
|
|
1112
1111
|
* *this* method's raw text, `createMeshLoader` receives urdf-loader's
|
|
@@ -1236,7 +1235,7 @@ interface TokenStore {
|
|
|
1236
1235
|
/** The default store: works out of the box, forgets the session on reload. */
|
|
1237
1236
|
declare class InMemoryTokenStore implements TokenStore {
|
|
1238
1237
|
#private;
|
|
1239
|
-
/**
|
|
1238
|
+
/** Nothing is loaded from anywhere — a client built with it starts logged out. */
|
|
1240
1239
|
constructor();
|
|
1241
1240
|
/** Returns the session held in memory, or `null` if there is none. */
|
|
1242
1241
|
load(): StoredSession | null;
|
|
@@ -1680,8 +1679,8 @@ interface CameraLiveSession {
|
|
|
1680
1679
|
* courteous fast path; the robot actually stops publishing once every
|
|
1681
1680
|
* viewer's LiveKit `Room` has disconnected, which the SFU notices on its
|
|
1682
1681
|
* own with no cooperation required. **Always pair this with disconnecting
|
|
1683
|
-
* the `Room` you connected with `url`/`token`** — see the
|
|
1684
|
-
*
|
|
1682
|
+
* the `Room` you connected with `url`/`token`** — see the Cameras
|
|
1683
|
+
* section of the SDK reference for the paired cleanup pattern; a `release()` that ran
|
|
1685
1684
|
* alone while the `Room` stayed connected would stop nothing.
|
|
1686
1685
|
*
|
|
1687
1686
|
* Safe to call more than once (only the first call does anything) and
|
|
@@ -1692,14 +1691,12 @@ interface CameraLiveSession {
|
|
|
1692
1691
|
* from `beforeunload`, where a call that could throw would be a liability.
|
|
1693
1692
|
*
|
|
1694
1693
|
* **A failed DELETE here is not observable anywhere** — not as a
|
|
1695
|
-
* rejection,
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
* reach the cloud" (telemetry, say), that is a new, additive signal to
|
|
1702
|
-
* design, not a change to this method's contract.
|
|
1694
|
+
* rejection, a realtime event, or a field on this object. Deliberate, not
|
|
1695
|
+
* an oversight: the only consumer of that information would be code
|
|
1696
|
+
* deciding whether to retry, and the reconciliation backstop described
|
|
1697
|
+
* above already makes a retry unnecessary for correctness. A future need
|
|
1698
|
+
* to know "did my release actually reach the cloud" (telemetry, say) is a
|
|
1699
|
+
* new, additive signal to design, not a change to this method's contract.
|
|
1703
1700
|
*/
|
|
1704
1701
|
release(): Promise<void>;
|
|
1705
1702
|
}
|
|
@@ -1753,12 +1750,11 @@ interface DatapointSubscriptionHandlers {
|
|
|
1753
1750
|
interface DatapointSubscription {
|
|
1754
1751
|
/**
|
|
1755
1752
|
* Stops this subscription. The `unsubscribe` frame reaches the server only
|
|
1756
|
-
* when this was the **last** holder of the robot
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
* once.
|
|
1753
|
+
* when this was the **last** holder of the robot/slug pair and the channel
|
|
1754
|
+
* is connected — subscriptions are reference-counted across kinds, so
|
|
1755
|
+
* releasing this one while a `datapoints.subscribe`, `actions.subscribe` or
|
|
1756
|
+
* in-flight `services.call` still holds the same pair leaves that one's
|
|
1757
|
+
* stream running. Safe to call more than once.
|
|
1762
1758
|
*/
|
|
1763
1759
|
unsubscribe(): void;
|
|
1764
1760
|
}
|
|
@@ -1766,11 +1762,10 @@ interface DatapointSubscription {
|
|
|
1766
1762
|
* Window aggregation for `datapoints.history`. `window` and `agg` always
|
|
1767
1763
|
* travel together on the wire — the cloud refuses one without the other
|
|
1768
1764
|
* rather than defaulting either, since a silently chosen aggregation is a
|
|
1769
|
-
* chart that lies quietly — so they live in one object
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
* merely rejected.
|
|
1765
|
+
* chart that lies quietly — so they live in one object instead of two
|
|
1766
|
+
* optional fields a caller could set only one of. Same reasoning as
|
|
1767
|
+
* `cameraSource`'s discriminated union: make the impossible combination
|
|
1768
|
+
* unrepresentable, not merely rejected.
|
|
1774
1769
|
*/
|
|
1775
1770
|
interface HistoryAggregation {
|
|
1776
1771
|
/** Bucket width, e.g. `10s`, `1m`. */
|
|
@@ -1781,9 +1776,8 @@ interface HistoryAggregation {
|
|
|
1781
1776
|
field?: string;
|
|
1782
1777
|
}
|
|
1783
1778
|
/**
|
|
1784
|
-
* The window `datapoints.history` reads
|
|
1785
|
-
* samples or
|
|
1786
|
-
* the two responses you get.
|
|
1779
|
+
* The window `datapoints.history` reads. `aggregate` decides whether the
|
|
1780
|
+
* response is raw samples or aggregated buckets.
|
|
1787
1781
|
*/
|
|
1788
1782
|
interface HistoryOptions {
|
|
1789
1783
|
/**
|
|
@@ -1823,9 +1817,9 @@ interface DatapointsApi {
|
|
|
1823
1817
|
* Reference-counted per `(robotId, slug)`: two subscriptions to the same
|
|
1824
1818
|
* pair share one wire subscription. Unsubscribing one never affects the
|
|
1825
1819
|
* other — the `unsubscribe` frame is sent only when the last subscriber
|
|
1826
|
-
* on that pair goes away.
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
1820
|
+
* on that pair goes away. In practice: two widgets showing the same
|
|
1821
|
+
* battery value, or a component mounted twice under React StrictMode,
|
|
1822
|
+
* subscribe to the same key. That count is shared with
|
|
1829
1823
|
* `actions.subscribe` and `services.call` — a slug is one namespace across
|
|
1830
1824
|
* kinds, and so is its subscription.
|
|
1831
1825
|
*/
|
|
@@ -1838,10 +1832,9 @@ interface DatapointsApi {
|
|
|
1838
1832
|
* reduced by `aggregate.agg`. Leave `aggregate` out and the other overload
|
|
1839
1833
|
* gives you raw samples instead.
|
|
1840
1834
|
*
|
|
1841
|
-
* Two overloads rather than one union so a caller who already knows which
|
|
1842
|
-
* they asked for isn't forced to narrow
|
|
1843
|
-
*
|
|
1844
|
-
* holding the result dynamically can still branch on it.
|
|
1835
|
+
* Two overloads rather than one union, so a caller who already knows which
|
|
1836
|
+
* one they asked for isn't forced to narrow it. `kind` carries the same
|
|
1837
|
+
* information either way, so dynamic code can still branch on it.
|
|
1845
1838
|
*
|
|
1846
1839
|
* **Rejects, does not silently empty out, two specific refusals** —
|
|
1847
1840
|
* unlike `cameras.snapshot`'s absorption of `no_snapshot_yet` into a null
|
|
@@ -1875,28 +1868,26 @@ interface DatapointsApi {
|
|
|
1875
1868
|
}
|
|
1876
1869
|
|
|
1877
1870
|
/**
|
|
1878
|
-
* Robot-wide job reads, reachable as `client.jobs
|
|
1879
|
-
*
|
|
1880
|
-
* `services` cannot do.
|
|
1871
|
+
* Robot-wide job reads, reachable as `client.jobs` — addressed by robot,
|
|
1872
|
+
* not slug, which `actions` and `services` cannot do.
|
|
1881
1873
|
*/
|
|
1882
1874
|
interface JobsApi {
|
|
1883
1875
|
/**
|
|
1884
1876
|
* Every job the platform currently believes this robot has.
|
|
1885
1877
|
*
|
|
1886
|
-
* `actions.subscribe`/`services.call` and `GET /jobs/:slug`
|
|
1887
|
-
* route those build on
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
1898
|
-
*
|
|
1899
|
-
* a robot doing nothing resolves `[]`.
|
|
1878
|
+
* `actions.subscribe`/`services.call` and `GET /jobs/:slug` — the per-slug
|
|
1879
|
+
* route those build on — all require knowing the slug already. Two cases
|
|
1880
|
+
* don't: a reconnecting bridge naming a job the cloud only *adopted*, and
|
|
1881
|
+
* a config change leaving a job on a slug the published document no
|
|
1882
|
+
* longer contains. Neither has a slug to give — this method is the only
|
|
1883
|
+
* way an app developer reaches them.
|
|
1884
|
+
*
|
|
1885
|
+
* At most one entry per slug: the current job there, same as a per-slug
|
|
1886
|
+
* read would answer. Not a history endpoint. Grant-filtered same as
|
|
1887
|
+
* `cameras.list`/`datapoints` — an end user or server key sees only jobs
|
|
1888
|
+
* on slugs their role grants, a developer session sees every job on the
|
|
1889
|
+
* robot. Never empty-vs-missing ambiguity: a robot doing nothing
|
|
1890
|
+
* resolves `[]`.
|
|
1900
1891
|
*
|
|
1901
1892
|
* **Ordered newest first by `started_at`, with `job.seq` as the
|
|
1902
1893
|
* tiebreaker** (`started_at` alone is not a total order — two jobs minted
|
|
@@ -1920,16 +1911,15 @@ interface PublishersApi {
|
|
|
1920
1911
|
/**
|
|
1921
1912
|
* Publishes one message to a publisher.
|
|
1922
1913
|
*
|
|
1923
|
-
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1926
|
-
*
|
|
1927
|
-
*
|
|
1928
|
-
*
|
|
1929
|
-
*
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
* section before building a publisher-driven control loop.
|
|
1914
|
+
* A plain method call — deliberately no deadman switch, rate governor or
|
|
1915
|
+
* "takt" helper. The bridge's own `timeout_ms` failsafe is the platform's
|
|
1916
|
+
* safety primitive: when messages stop arriving, crash included, the
|
|
1917
|
+
* bridge publishes its configured failsafe message. That does **not**
|
|
1918
|
+
* cover a caller who stops calling `publish` on purpose without stopping
|
|
1919
|
+
* cleanly (e.g. no repeated call at a safe rate) — how often and when to
|
|
1920
|
+
* publish is the app's pattern, not the SDK's. See the Publishers
|
|
1921
|
+
* section of the SDK reference before building a
|
|
1922
|
+
* publisher-driven control loop.
|
|
1933
1923
|
*
|
|
1934
1924
|
* Rejects `publisher_busy` while a different user is publishing and has
|
|
1935
1925
|
* not been quiet for its configured quiet timeout yet — whoever publishes
|
|
@@ -1940,16 +1930,15 @@ interface PublishersApi {
|
|
|
1940
1930
|
|
|
1941
1931
|
/**
|
|
1942
1932
|
* Request/response calls to a robot, reachable as `client.services`. A
|
|
1943
|
-
* service answers once and is done
|
|
1944
|
-
* single method and nothing to subscribe to.
|
|
1933
|
+
* service answers once and is done — one method, nothing to subscribe to.
|
|
1945
1934
|
*/
|
|
1946
1935
|
interface ServicesApi {
|
|
1947
1936
|
/**
|
|
1948
1937
|
* Calls a service and resolves with its result. A service call is a job
|
|
1949
1938
|
* underneath — the same `job_id` exchange and disconnect survival as an
|
|
1950
1939
|
* action — but that is deliberately invisible here: the caller gets a
|
|
1951
|
-
* plain `Promise<result>`, matching the REST `serviceCallResponse` shape
|
|
1952
|
-
*
|
|
1940
|
+
* plain `Promise<result>`, matching the REST `serviceCallResponse` shape.
|
|
1941
|
+
* There is nothing to subscribe to for a service —
|
|
1953
1942
|
* no feedback, no progress, no cancel — so this call already waits for
|
|
1954
1943
|
* the terminal state internally.
|
|
1955
1944
|
*
|
|
@@ -1962,8 +1951,7 @@ interface ServicesApi {
|
|
|
1962
1951
|
* call — the ack that a job was created, plus however much of the
|
|
1963
1952
|
* budget is left for it to then reach a terminal state — not two
|
|
1964
1953
|
* separate `timeoutMs`-length windows back to back. A caller who sets
|
|
1965
|
-
* `timeoutMs: 5000`
|
|
1966
|
-
* number means what it says, once, for the whole call.
|
|
1954
|
+
* `timeoutMs: 5000` bounds total latency at ~5s, not ~10s.
|
|
1967
1955
|
*
|
|
1968
1956
|
* It is also **not independent** of `patienceMs`: left unset, it
|
|
1969
1957
|
* is derived from `patienceMs` so this SDK's local clock cannot fire
|
|
@@ -1986,10 +1974,9 @@ interface FleetlessClientOptions {
|
|
|
1986
1974
|
/** The app's identifier (the slug shown in the console), sent on every login. */
|
|
1987
1975
|
appIdentifier: string;
|
|
1988
1976
|
/**
|
|
1989
|
-
* Where refresh/access tokens
|
|
1990
|
-
*
|
|
1991
|
-
*
|
|
1992
|
-
* browser exists.
|
|
1977
|
+
* Where refresh/access tokens live between calls. Defaults to in-memory —
|
|
1978
|
+
* pass your own (localStorage, a cookie, a native keystore) to persist a
|
|
1979
|
+
* session across reloads. The SDK never assumes a browser exists.
|
|
1993
1980
|
*/
|
|
1994
1981
|
tokenStore?: TokenStore;
|
|
1995
1982
|
/**
|
|
@@ -2051,13 +2038,13 @@ interface FleetlessClient {
|
|
|
2051
2038
|
/** URDF and mesh reads — list/get/urdf, plus the `urdf-loader` mesh callback. */
|
|
2052
2039
|
readonly assets: AssetsApi;
|
|
2053
2040
|
/**
|
|
2054
|
-
* Closes the realtime channel and stops it from reconnecting. Safe
|
|
2055
|
-
*
|
|
2056
|
-
*
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2059
|
-
*
|
|
2060
|
-
*
|
|
2041
|
+
* Closes the realtime channel and stops it from reconnecting. Safe with
|
|
2042
|
+
* no subscription ever made, and safe to call twice. A Node script (the
|
|
2043
|
+
* exact use case `serverKey` is for) that never calls this after
|
|
2044
|
+
* subscribing will not exit on its own — an open WebSocket keeps the
|
|
2045
|
+
* event loop alive. `auth.logout()` calls this automatically; call it
|
|
2046
|
+
* yourself if the process should exit without logging out (e.g. a
|
|
2047
|
+
* server-side shutdown).
|
|
2061
2048
|
*/
|
|
2062
2049
|
close(): void;
|
|
2063
2050
|
}
|
package/dist/index.js
CHANGED
|
@@ -304,7 +304,7 @@ function createAssetsApi(http) {
|
|
|
304
304
|
finish(
|
|
305
305
|
null,
|
|
306
306
|
new Error(
|
|
307
|
-
`createMeshLoader and prepareUrdfScene were both installed on the same LoadingManager for robot ${robotId}. Use one or the other on a given manager, not both \u2014 see the
|
|
307
|
+
`createMeshLoader and prepareUrdfScene were both installed on the same LoadingManager for robot ${robotId}. Use one or the other on a given manager, not both \u2014 see the SDK reference.`
|
|
308
308
|
)
|
|
309
309
|
);
|
|
310
310
|
return;
|
|
@@ -8302,21 +8302,21 @@ function assertValidJobId(jobId) {
|
|
|
8302
8302
|
if (jobId === void 0 || jobId === null || typeof jobId === "string") return;
|
|
8303
8303
|
throw new FleetlessError(
|
|
8304
8304
|
"invalid_option",
|
|
8305
|
-
`cancel()'s third argument must be a job id (string), null, or omitted \u2014 got ${typeof jobId === "object" ? "an object" : typeof jobId}. If you are passing an options object (e.g. {timeoutMs}) as the third argument, note the signature changed in this release: cancel(robotId, slug) is unchanged, but a third positional argument is now the job id to cancel and options moved to a fourth argument \u2014 cancel(robotId, slug, jobId, options). See the
|
|
8305
|
+
`cancel()'s third argument must be a job id (string), null, or omitted \u2014 got ${typeof jobId === "object" ? "an object" : typeof jobId}. If you are passing an options object (e.g. {timeoutMs}) as the third argument, note the signature changed in this release: cancel(robotId, slug) is unchanged, but a third positional argument is now the job id to cancel and options moved to a fourth argument \u2014 cancel(robotId, slug, jobId, options). See the Actions section of the SDK reference at https://docs.fleetless.dev/reference/sdk/`
|
|
8306
8306
|
);
|
|
8307
8307
|
}
|
|
8308
8308
|
function createRealtimeCommandTransport(channel) {
|
|
8309
8309
|
return {
|
|
8310
|
-
// Declared `async` deliberately, unlike `cancel`/`publish` below: it
|
|
8310
|
+
// Declared `async` deliberately, unlike `cancel`/`publish` below: it's
|
|
8311
8311
|
// the only one of the three that can refuse *before* sending anything
|
|
8312
|
-
// (`resolveLocalWaitMs`'s `invalid_option`), and every caller of
|
|
8313
|
-
//
|
|
8314
|
-
//
|
|
8315
|
-
//
|
|
8316
|
-
//
|
|
8317
|
-
//
|
|
8318
|
-
//
|
|
8319
|
-
//
|
|
8312
|
+
// (`resolveLocalWaitMs`'s `invalid_option`), and every caller of this
|
|
8313
|
+
// interface — starting with this file's own `sendCommand` callers —
|
|
8314
|
+
// assumes `CommandTransport.invoke` always returns a promise, never
|
|
8315
|
+
// throws synchronously. Without `async` here, a synchronous throw from
|
|
8316
|
+
// `resolveLocalWaitMs` would escape as a thrown exception instead of a
|
|
8317
|
+
// rejection, breaking that assumption for a caller that isn't itself
|
|
8318
|
+
// inside an `async` function (e.g. a test calling this transport
|
|
8319
|
+
// directly).
|
|
8320
8320
|
async invoke(robotId, slug2, params, options) {
|
|
8321
8321
|
const timeoutMs = resolveLocalWaitMs(options ?? {}, DEFAULT_COMMAND_TIMEOUT_MS);
|
|
8322
8322
|
const frame = {
|
|
@@ -8516,12 +8516,11 @@ var RealtimeChannel = class {
|
|
|
8516
8516
|
}
|
|
8517
8517
|
/**
|
|
8518
8518
|
* Increments on every successful authentication (first connect and every
|
|
8519
|
-
* reconnect). A command sent on one
|
|
8520
|
-
*
|
|
8521
|
-
*
|
|
8522
|
-
*
|
|
8523
|
-
*
|
|
8524
|
-
* than after a timeout elapses.
|
|
8519
|
+
* reconnect). A command sent on one socket can only be answered on that
|
|
8520
|
+
* socket — comparing the epoch at send time against the current one is
|
|
8521
|
+
* how a caller (see `commands.ts`) tells "still waiting on the connection
|
|
8522
|
+
* it was sent over" from "that connection is gone and a new one has taken
|
|
8523
|
+
* its place", the moment it happens rather than after a timeout elapses.
|
|
8525
8524
|
*/
|
|
8526
8525
|
get connectionEpoch() {
|
|
8527
8526
|
return this.#connectionEpoch;
|
|
@@ -8822,7 +8821,7 @@ function createSlugSubscriptions(channel) {
|
|
|
8822
8821
|
// src/token-store.ts
|
|
8823
8822
|
var InMemoryTokenStore = class {
|
|
8824
8823
|
#session = null;
|
|
8825
|
-
/**
|
|
8824
|
+
/** Nothing is loaded from anywhere — a client built with it starts logged out. */
|
|
8826
8825
|
constructor() {
|
|
8827
8826
|
}
|
|
8828
8827
|
/** Returns the session held in memory, or `null` if there is none. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/sdk",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "The official TypeScript SDK for Fleetless client apps
|
|
3
|
+
"version": "3.0.3",
|
|
4
|
+
"description": "The official TypeScript SDK for Fleetless client apps — a ROS 2 robot as a REST and realtime API, cameras, jobs, and the app's own user accounts, federated sign-in and MCP consent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Dehne Robotik GmbH",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"url": "https://dehne-robotik.de"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://docs.fleetless.dev/reference/sdk/",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/fleetless/sdk"
|
|
15
|
+
},
|
|
12
16
|
"bugs": {
|
|
13
17
|
"email": "hello@fleetless.dev"
|
|
14
18
|
},
|
|
@@ -51,8 +55,7 @@
|
|
|
51
55
|
"CHANGELOG.md",
|
|
52
56
|
"SECURITY.md",
|
|
53
57
|
"CONTRIBUTING.md",
|
|
54
|
-
"CODE_OF_CONDUCT.md"
|
|
55
|
-
"RELEASING.md"
|
|
58
|
+
"CODE_OF_CONDUCT.md"
|
|
56
59
|
],
|
|
57
60
|
"engines": {
|
|
58
61
|
"node": ">=20"
|
|
@@ -61,6 +64,7 @@
|
|
|
61
64
|
"scripts": {
|
|
62
65
|
"build": "tsup && node scripts/stamp-dist.mjs",
|
|
63
66
|
"prepare": "tsup && node scripts/stamp-dist.mjs",
|
|
67
|
+
"prepublishOnly": "node scripts/refuse-manual-publish.mjs",
|
|
64
68
|
"typecheck": "tsc --noEmit",
|
|
65
69
|
"test": "vitest run",
|
|
66
70
|
"test:pack": "node scripts/verify-published-types.mjs",
|
package/RELEASING.md
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# Releasing `@fleetless/sdk`
|
|
2
|
-
|
|
3
|
-
Maintainer notes: the development setup, the checks, and the release
|
|
4
|
-
procedure. This file is not part of the published package.
|
|
5
|
-
|
|
6
|
-
**CI runs on GitLab; the repository will mirror to GitHub.** The pipeline
|
|
7
|
-
that verifies and publishes this package lives on an internal GitLab instance,
|
|
8
|
-
and that is the only thing that publishes to npm. The public repository does not
|
|
9
|
-
exist yet; when it does it is a mirror, where issues and pull requests arrive
|
|
10
|
-
and no check runs. A contributor's pull request is verified by a maintainer
|
|
11
|
-
running the same commands locally — see
|
|
12
|
-
[CONTRIBUTING.md](CONTRIBUTING.md), which says so to the contributor as well.
|
|
13
|
-
|
|
14
|
-
A mirror pushes what it is given, so **everything in a commit becomes public
|
|
15
|
-
the moment it is pushed**, including the commit message and every file the
|
|
16
|
-
branch touched.
|
|
17
|
-
## Development setup
|
|
18
|
-
|
|
19
|
-
Node 22 via `nvm`, and pnpm through corepack:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
nvm use 22
|
|
23
|
-
corepack enable
|
|
24
|
-
pnpm install
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**That install needs nothing private.** `@fleetless/contracts` — the source
|
|
28
|
-
of truth for every wire type this SDK reads or writes — is a devDependency on
|
|
29
|
-
the public npm package, pinned to an exact version. It used to be a private
|
|
30
|
-
`git+ssh://` URL that only somebody with GitLab group access could install;
|
|
31
|
-
that is over. The published SDK is unaffected either way: `tsup` inlines
|
|
32
|
-
contracts' types into `dist/`, so a consumer of `@fleetless/sdk` never
|
|
33
|
-
resolves it.
|
|
34
|
-
|
|
35
|
-
Never redefine a shape the SDK sends to or reads from the API. Import it from
|
|
36
|
-
`@fleetless/contracts` instead.
|
|
37
|
-
|
|
38
|
-
## Checks
|
|
39
|
-
|
|
40
|
-
| Command | What it does |
|
|
41
|
-
|---|---|
|
|
42
|
-
| `pnpm typecheck` | `tsc --noEmit` over the package. |
|
|
43
|
-
| `pnpm test` | vitest. Most suites drive a fake `fetch` and a fake WebSocket; the three auth suites drive the SDK's own default `fetch` against a real `node:http` server (`test/local-api.ts`). No cloud needed. |
|
|
44
|
-
| `pnpm build` | tsup into `dist/` — ESM, CJS and `.d.ts`. |
|
|
45
|
-
| `pnpm run test:pack` | `npm pack`s the tarball, installs it into a bare project with nothing but `typescript`, and typechecks and runs real usage against it. This is what catches a `dist/index.d.ts` that still imports from `@fleetless/contracts` — a devDependency, so a consumer of the SDK never installs it. |
|
|
46
|
-
|
|
47
|
-
Two further scripts check the built package against a **running** cloud
|
|
48
|
-
(`./infra/dev.sh` in the umbrella repo). Each one runs against `dist/`, not
|
|
49
|
-
`src/`, so run `pnpm build` first, and each fails by name on a missing
|
|
50
|
-
variable rather than defaulting quietly.
|
|
51
|
-
|
|
52
|
-
- `pnpm run verify:live` — a `busy` refusal carrying the job that is already
|
|
53
|
-
running, `command_outcome_unknown` and its documented recovery, a
|
|
54
|
-
`parameter_invalid` naming the flat key, job-id-addressed cancel, and
|
|
55
|
-
per-session camera release. Needs `FLEETLESS_API_URL` (or `API`),
|
|
56
|
-
`APP_IDENTIFIER`, `EMAIL`, `PASSWORD`, `ROBOT_ID`, `ACTION_SLUG`,
|
|
57
|
-
`SERVICE_SLUG`, `CAMERA_SLUG`, and two optional identities:
|
|
58
|
-
- `SECOND_EMAIL` — a second, distinct **app user** with the same role,
|
|
59
|
-
without which the busy check only proves that a second request is refused,
|
|
60
|
-
not that a different user's is.
|
|
61
|
-
- `OBSERVER_EMAIL` — a **third** app user, and the one whose password check
|
|
62
|
-
[6] rotates and restores. Leave it unset and the round trip runs on
|
|
63
|
-
`EMAIL`, the identity every other check in the file signs in as; the
|
|
64
|
-
script says so out loud when that happens. `infra/seed-dev.mjs --env`
|
|
65
|
-
exports all three.
|
|
66
|
-
|
|
67
|
-
Since 3.0.0 it also drives the client auth API as check [6]: `listProviders`
|
|
68
|
-
without a session, `login`, `me` (kind, app, role, address), `logout`, the
|
|
69
|
-
`changePassword` round trip with its restore, and the reset acknowledgement
|
|
70
|
-
compared byte for byte across a known and an unknown address. What it does
|
|
71
|
-
**not** drive is anything needing a mailed token — `register`,
|
|
72
|
-
`verifyEmail`, `acceptInvitation`, `confirmPasswordReset` — or the federated
|
|
73
|
-
and MCP-consent flows. Those are `infra/browser/app-auth-check.mjs`'s
|
|
74
|
-
subject, which reads maildev and drives a real Keycloak.
|
|
75
|
-
- `pnpm run verify:history` — a relative range and its absolute equivalent
|
|
76
|
-
returning the same samples, aggregation matching arithmetic done here from
|
|
77
|
-
the raw rows, and the `not_recorded` / `not_aggregatable` refusals. Needs
|
|
78
|
-
`FLEETLESS_API_URL`, `APP_IDENTIFIER`, `EMAIL`, `PASSWORD`, `ROBOT_ID`,
|
|
79
|
-
`RECORDED_NUMERIC_SLUG`, `LIVE_ONLY_SLUG`, and optionally
|
|
80
|
-
`NON_NUMERIC_RECORDED_SLUG`.
|
|
81
|
-
(`verify:hosted-login` is gone. The hosted login flow it drove —
|
|
82
|
-
`beginHostedLogin` / `completeHostedLogin` and the app OAuth client behind
|
|
83
|
-
them — was removed in 3.0.0 along with the script and its `package.json`
|
|
84
|
-
entry.)
|
|
85
|
-
|
|
86
|
-
`infra/seed-dev.mjs --env` in the umbrella repo exports most of those
|
|
87
|
-
variables for a freshly seeded world.
|
|
88
|
-
|
|
89
|
-
## Commits
|
|
90
|
-
|
|
91
|
-
[Conventional Commits](https://www.conventionalcommits.org/). English, for
|
|
92
|
-
code, comments, commit messages and everything else that lands in the
|
|
93
|
-
repository.
|
|
94
|
-
|
|
95
|
-
## Releasing
|
|
96
|
-
|
|
97
|
-
Every version on npm is published by this repository's GitLab pipeline from a
|
|
98
|
-
release tag. `npm publish` by hand is retired.
|
|
99
|
-
|
|
100
|
-
1. Add the version's entry to `CHANGELOG.md` and set `version` in
|
|
101
|
-
`package.json` to the same number.
|
|
102
|
-
2. Commit (`chore(release): X.Y.Z`), push, and wait for the branch pipeline's
|
|
103
|
-
`verify` job to go green.
|
|
104
|
-
3. `git tag vX.Y.Z && git push origin vX.Y.Z`. The tag pipeline runs `verify`
|
|
105
|
-
again and then `publish`.
|
|
106
|
-
4. Check the registry yourself. The `publish` job already asserts the first
|
|
107
|
-
line; this is the independent look, and the second line is the one that
|
|
108
|
-
says which dist-tag moved.
|
|
109
|
-
|
|
110
|
-
```sh
|
|
111
|
-
npm view @fleetless/sdk@X.Y.Z version # answers X.Y.Z
|
|
112
|
-
npm view @fleetless/sdk dist-tags # latest -> X.Y.Z, or next -> X.Y.Z
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Name the version. A bare `npm view @fleetless/sdk version` resolves the
|
|
116
|
-
`latest` dist-tag, so after a pre-release publish it answers the *previous*
|
|
117
|
-
stable release and reads as a publish that did not happen.
|
|
118
|
-
|
|
119
|
-
A pre-release tag — `vX.Y.Z-beta.1`, `vX.Y.Z-rc.2` — publishes under the npm
|
|
120
|
-
dist-tag `next` instead of `latest`. Nothing else about it differs, and it is
|
|
121
|
-
exactly why step 4 names the version.
|
|
122
|
-
|
|
123
|
-
**A red `publish` job does not mean nothing was published.** The job runs
|
|
124
|
-
`npm publish` and then looks the version up on the registry for about two
|
|
125
|
-
minutes; npm answers reads from a replica that lags a publish, so the lookup
|
|
126
|
-
can time out on a version that did land. The job says so itself, and it is
|
|
127
|
-
worth repeating here because a red pipeline invites exactly one reaction —
|
|
128
|
-
press retry — and that reaction cannot work: npm refuses to publish over an
|
|
129
|
-
existing version, so the retry ends in a 403 that reads like a broken
|
|
130
|
-
pipeline rather than like a release that already happened.
|
|
131
|
-
|
|
132
|
-
> publish may have succeeded; the registry has not served the version yet; do
|
|
133
|
-
> NOT retry this job (npm refuses to republish a version) — check
|
|
134
|
-
> `npm view @fleetless/sdk@$VERSION` by hand
|
|
135
|
-
|
|
136
|
-
If the hand check answers the version, the release is done: move the dist-tag
|
|
137
|
-
by hand if it is wrong (`npm dist-tag add @fleetless/sdk@X.Y.Z latest`) and
|
|
138
|
-
leave the job red. If it answers nothing after several minutes, the publish
|
|
139
|
-
genuinely did not land and the job can be retried.
|
|
140
|
-
|
|
141
|
-
**A red `publish` job that ends in `npm error code EOTP` published nothing.**
|
|
142
|
-
npm is asking for a one-time password, which a pipeline cannot supply: the
|
|
143
|
-
token in `NPM_TOKEN` does not bypass two-factor authentication, or the
|
|
144
|
-
package's *Publishing access* setting on npmjs.com disallows tokens. Fix the
|
|
145
|
-
token (a granular access token created with *Bypass two-factor
|
|
146
|
-
authentication*) or the package setting (*Require two-factor authentication
|
|
147
|
-
or an automation token*), then retry the job — nothing reached the registry,
|
|
148
|
-
so a retry is safe here — measured on a pipeline that hit it.
|
|
149
|
-
|
|
150
|
-
**The pipeline refuses a tag whose version disagrees with `package.json`.**
|
|
151
|
-
`scripts/verify-version-tag.mjs` is the one place that rule lives; `verify`
|
|
152
|
-
runs it first on a tag pipeline, so a mistyped tag fails in seconds and
|
|
153
|
-
`publish` never starts (measured: `verify-version-tag: tag v9.9.9 names
|
|
154
|
-
9.9.9 but package.json says 1.0.0`, publish skipped).
|
|
155
|
-
|
|
156
|
-
Removing that bad tag takes the API, not git. `v*` is a **protected** tag
|
|
157
|
-
pattern, so a delete over git is refused by the server with nothing but
|
|
158
|
-
`! [remote rejected] v9.9.9 (pre-receive hook declined)`:
|
|
159
|
-
|
|
160
|
-
```sh
|
|
161
|
-
git tag -d vX.Y.Z # local
|
|
162
|
-
glab api -X DELETE projects/37/repository/tags/vX.Y.Z # remote
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Then fix `package.json` and tag again.
|
|
166
|
-
|
|
167
|
-
### The one-time setting
|
|
168
|
-
|
|
169
|
-
It is already in place; it is written down because nothing in this repository
|
|
170
|
-
would tell you it exists if it were removed.
|
|
171
|
-
|
|
172
|
-
There used to be a second one — `fleetless/fleetless-sdk` on the job-token
|
|
173
|
-
allow-list of `fleetless/fleetless-contracts`, so the pipeline could rewrite
|
|
174
|
-
the private `git+ssh://` contracts URL to HTTPS with `CI_JOB_TOKEN`. Contracts
|
|
175
|
-
is a public npm package now, the rewrite is gone from `.gitlab-ci.yml`, and
|
|
176
|
-
the allow-list entry buys this project nothing. Removing it is safe; leaving
|
|
177
|
-
it is harmless.
|
|
178
|
-
|
|
179
|
-
- **`NPM_TOKEN` is a protected, masked CI variable on this project**, holding
|
|
180
|
-
an npm granular automation token with publish rights on `@fleetless/sdk`.
|
|
181
|
-
Protected means an unprotected ref receives an *empty* value rather than no
|
|
182
|
-
value, which is why the tag pattern `v*` is a protected tag and why the
|
|
183
|
-
`publish` job's first action is to refuse an empty `NPM_TOKEN` by name.
|
|
184
|
-
|
|
185
|
-
```sh
|
|
186
|
-
glab api projects/37/protected_tags # v*, create access: Maintainers
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
The token reaches npm through an `.npmrc` written in the job's working
|
|
190
|
-
directory holding `//registry.npmjs.org/:_authToken=${NPM_TOKEN}` **literally**
|
|
191
|
-
— npm expands the variable when it reads the file, so the secret itself never
|
|
192
|
-
lands on disk, and `after_script` removes the file either way. It cannot be
|
|
193
|
-
passed as an environment assignment instead: `NPM_CONFIG_//registry…` is not a
|
|
194
|
-
valid shell identifier, so both `bash` and `dash` parse it as a command name.
|
|
195
|
-
|