@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/CHANGELOG.md +143 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +131 -0
- package/README.md +130 -6
- package/RELEASING.md +195 -0
- package/SECURITY.md +100 -0
- package/dist/index.cjs +376 -501
- package/dist/index.d.cts +57 -62
- package/dist/index.d.ts +57 -62
- package/dist/index.js +376 -501
- package/package.json +11 -6
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.
|