@fleetless/sdk 3.0.0 → 3.0.2

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/RELEASING.md ADDED
@@ -0,0 +1,195 @@
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
+
package/SECURITY.md ADDED
@@ -0,0 +1,100 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Email **security@fleetless.dev**. Please do not open a public GitHub issue for
6
+ a security report.
7
+
8
+ Include what you found, the SDK version and runtime it happens on (a browser
9
+ name and version, or a Node version), and the smallest sequence of SDK calls
10
+ that shows it. A reproduction against a published version of this package is
11
+ the most useful thing you can send.
12
+
13
+ We acknowledge every report within **3 working days** and follow up with
14
+ either a fix or a written plan within **30 days**. If a report leads to a
15
+ released fix, we credit you by name unless you ask us not to.
16
+
17
+ ## What is in scope
18
+
19
+ This repository is the TypeScript SDK that client applications use to talk to
20
+ Fleetless. It runs **in your users' browsers and on your servers**, and it
21
+ holds credentials while it does. That is where its security surface is.
22
+
23
+ Specifically in scope:
24
+
25
+ ### Tokens
26
+
27
+ The SDK receives an access token and a refresh token from the platform and
28
+ hands them to a `TokenStore` that **you** implement; the built-in default keeps
29
+ them in memory only and writes them nowhere. A report is in scope if the SDK:
30
+
31
+ - puts a token somewhere the caller did not ask for it to go — a global, a
32
+ URL, a query string, a log line, an error message, or a thrown object's
33
+ properties;
34
+ - sends a token to an origin other than the `apiUrl` the client was
35
+ constructed with;
36
+ - attaches a token to a request it should not have (the SDK refuses an
37
+ absolute URL it did not build — see `untrusted_absolute_url`);
38
+ - keeps a token reachable after `logout()`, or fails to clear the store;
39
+ - races its own silent refresh in a way that lets a stale or a foreign token
40
+ be used.
41
+
42
+ Choosing to persist tokens in `localStorage`, a cookie or a native keystore is
43
+ your decision and its consequences are yours; a defect in how the SDK *hands*
44
+ them to your store is ours.
45
+
46
+ ### PKCE and `state` in the federated sign-in flow
47
+
48
+ `beginOidcLogin` generates a PKCE verifier and a `state` value and returns them
49
+ to you, because a redirect is a fresh page load and the SDK has nowhere of its
50
+ own to keep them. `completeOidcLogin` checks `state` before it exchanges
51
+ anything.
52
+
53
+ In scope: a verifier or a `state` with insufficient entropy or a predictable
54
+ source; S256 not being enforced; `completeOidcLogin` exchanging a code when
55
+ `state` does not match, is missing, or when `expectedState` is empty — that
56
+ last case is checked first and explicitly, because two empty strings compare
57
+ equal; any path that reaches the token exchange without the check.
58
+
59
+ **Where you store the verifier and the `state` between the two calls is your
60
+ application's decision**, and this policy cannot cover it. The README shows
61
+ `sessionStorage`, which is a reasonable default and not the only correct one.
62
+
63
+ ### The rest of the package
64
+
65
+ - Request construction: path segments are percent-encoded (`pathSegment`), so
66
+ a slug or an identifier shaped like `../../../admin` must not escape its
67
+ position in the URL.
68
+ - The realtime channel: its authentication, its reconnection, and what it
69
+ resends after one.
70
+ - `prepareUrdfScene` and `createMeshLoader`: a robot's own URDF names the URLs
71
+ three.js is asked to fetch, so a reference the SDK does not own must not
72
+ become a network request from your users' browsers.
73
+ - Anything the SDK writes into browser-reachable state.
74
+ - The published npm package `@fleetless/sdk` and its contents, including a
75
+ dependency of it.
76
+
77
+ ## What is not in scope
78
+
79
+ **The Fleetless cloud is not in this repository.** A server that fails to
80
+ enforce a permission, an authentication or authorisation flaw in the platform,
81
+ a rate limit, a data leak from an API endpoint, anything about how a token is
82
+ minted or validated — none of those live here, and none of them can be fixed by
83
+ a change to this package. Report them to the same address; say which service
84
+ you were looking at, and we will route it. What we cannot do is treat this
85
+ repository's issue tracker as the place where they are tracked.
86
+
87
+ Also out of scope here: the Fleetless console, the robot-side bridge, the
88
+ `@fleetless/contracts` schemas (they have their own repository and their own
89
+ policy), the documentation site, and any deployment of Fleetless operated by
90
+ someone else.
91
+
92
+ Out of scope in your own application: where you store tokens, how you protect
93
+ your own pages, and any XSS in your app — an attacker who can run script in
94
+ your page can read anything your page can, and no SDK design prevents that.
95
+
96
+ ## Supported versions
97
+
98
+ The latest published minor of `@fleetless/sdk` receives fixes. Older minors do
99
+ not; a security fix is released as a new patch on the current minor, and
100
+ upgrading is the remedy.