[feat](client): scan an instance-relative join payload

The service mints /api/alarm/a/{alertId}#{token} with no host, so the QR renders against the configured instance and keeps working when the instance moves or is reached by another name. The scanner now matches path shape and fragment and never the host, so legacy alarm.dcntrlized.org codes and other instances codes still join; error copy distinguishes a malformed code from a wrong-alert token.
This commit is contained in:
2026-10-19 02:34:20 +01:00
parent fc66b8e088
commit dcfc9a5df8
5 changed files with 163 additions and 202 deletions
+7 -7
View File
@@ -19,9 +19,7 @@ function stubFetch(body: string, status = 400): void {
beforeEach(() => {
configureApi({
baseUrl: 'http://alarm.test',
mode: 'direct',
serviceRef: null,
baseUrl: 'http://instance.test',
token: null,
refreshToken: null,
onUnauthorized: () => {},
@@ -59,13 +57,15 @@ describe('apiClient error unwrapping', () => {
});
describe('joinErrorMessage', () => {
it('renders a token problem as a remedy, never as "expired"', () => {
// JoinAlert returns this one code for a rotated token AND for a malformed
// one, so the client cannot say which happened.
it('says the code is not valid for this alert, without inventing a replacement', () => {
// JoinAlert returns this one code for any code that does not belong to the
// alert, so the client cannot say which happened — and with rotation gone
// there is no "new one" to ask for.
const err = new ApiError(400, 'invalid subscribe token', 'ERR_ALERT_TOKEN_INVALID');
const copy = joinErrorMessage(err);
expect(copy).toBe('This code is no longer valid — ask the owner for a new one.');
expect(copy).toBe('This code is not valid for this alert.');
expect(copy).not.toMatch(/expired/i);
expect(copy).not.toMatch(/new one|rotate|rotated/i);
});
it('passes through any other server message', () => {