ntfy/web/src/app/Api.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

179 lines
5.5 KiB
JavaScript
Raw Normal View History

import {
fetchLinesIterator,
maybeWithAuth,
topicShortUrl,
topicUrl,
topicUrlAuth,
topicUrlJsonPoll,
topicUrlJsonPollWithSince,
topicUrlWebPushSubscribe,
topicUrlWebPushUnsubscribe,
webPushConfigUrl,
} from "./utils";
import userManager from "./UserManager";
2023-02-03 09:19:37 +13:00
import { fetchOrThrow } from "./errors";
2022-02-23 17:22:30 +13:00
class Api {
async poll(baseUrl, topic, since) {
const user = await userManager.get(baseUrl);
const shortUrl = topicShortUrl(baseUrl, topic);
const url = since ? topicUrlJsonPollWithSince(baseUrl, topic, since) : topicUrlJsonPoll(baseUrl, topic);
2022-02-23 17:22:30 +13:00
const messages = [];
const headers = maybeWithAuth({}, user);
2022-02-23 17:22:30 +13:00
console.log(`[Api] Polling ${url}`);
for await (const line of fetchLinesIterator(url, headers)) {
2023-05-16 05:37:30 +12:00
const message = JSON.parse(line);
if (message.id) {
console.log(`[Api, ${shortUrl}] Received message ${line}`);
messages.push(message);
2022-02-23 17:22:30 +13:00
}
}
2022-02-25 08:53:45 +13:00
return messages;
2023-05-24 07:13:01 +12:00
}
2022-02-23 17:22:30 +13:00
async publish(baseUrl, topic, message, options) {
const user = await userManager.get(baseUrl);
console.log(`[Api] Publishing message to ${topicUrl(baseUrl, topic)}`);
2022-03-11 16:58:24 +13:00
const headers = {};
const body = {
topic,
message,
...options,
};
2023-02-03 09:19:37 +13:00
await fetchOrThrow(baseUrl, {
2022-02-23 17:22:30 +13:00
method: "PUT",
body: JSON.stringify(body),
headers: maybeWithAuth(headers, user),
2022-02-23 17:22:30 +13:00
});
}
2022-02-26 07:40:03 +13:00
2022-04-06 11:55:43 +12:00
/**
* Publishes to a topic using XMLHttpRequest (XHR), and returns a Promise with the active request.
* Unfortunately, fetch() does not support a progress hook, which is why XHR has to be used.
*
* Firefox XHR bug:
* Firefox has a bug(?), which returns 0 and "" for all fields of the XHR response in the case of an error,
* so we cannot determine the exact error. It also sometimes complains about CORS violations, even when the
* correct headers are clearly set. It's quite the odd behavior.
*
* There is an example, and the bug report here:
* - https://bugzilla.mozilla.org/show_bug.cgi?id=1733755
* - https://gist.github.com/binwiederhier/627f146d1959799be207ad8c17a8f345
*/
publishXHR(url, body, headers, onProgress) {
2022-04-02 01:41:45 +13:00
console.log(`[Api] Publishing message to ${url}`);
const xhr = new XMLHttpRequest();
2022-04-02 01:41:45 +13:00
const send = new Promise((resolve, reject) => {
xhr.open("PUT", url);
2022-04-04 12:19:43 +12:00
if (body.type) {
xhr.overrideMimeType(body.type);
2023-05-24 07:13:01 +12:00
}
2022-04-04 12:19:43 +12:00
for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value);
2023-05-24 07:13:01 +12:00
}
2022-04-04 12:19:43 +12:00
xhr.upload.addEventListener("progress", onProgress);
xhr.addEventListener("readystatechange", () => {
2022-04-02 01:41:45 +13:00
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) {
console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response);
2022-04-02 01:41:45 +13:00
resolve(xhr.response);
} else if (xhr.readyState === 4) {
2022-04-06 11:55:43 +12:00
// Firefox bug; see description above!
2022-04-04 11:51:32 +12:00
console.log(`[Api] Publish failed (HTTP ${xhr.status})`, xhr.responseText);
let errorText;
2023-05-24 07:13:01 +12:00
try {
2022-04-04 11:51:32 +12:00
const error = JSON.parse(xhr.responseText);
if (error.code && error.error) {
errorText = `Error ${error.code}: ${error.error}`;
2022-04-04 12:19:43 +12:00
}
2022-04-02 01:41:45 +13:00
} catch (e) {
2022-04-04 11:51:32 +12:00
// Nothing
}
2022-04-02 01:41:45 +13:00
xhr.abort();
2022-04-04 11:51:32 +12:00
reject(errorText ?? "An error occurred");
2022-04-02 01:41:45 +13:00
}
2023-05-24 07:13:01 +12:00
});
2022-04-02 01:41:45 +13:00
xhr.send(body);
2023-05-24 07:13:01 +12:00
});
2022-04-02 01:41:45 +13:00
send.abort = () => {
console.log(`[Api] Publish aborted by user`);
xhr.abort();
2023-05-24 07:13:01 +12:00
};
2022-04-02 01:41:45 +13:00
return send;
}
2022-12-03 09:37:48 +13:00
async topicAuth(baseUrl, topic, user) {
2022-02-26 07:40:03 +13:00
const url = topicUrlAuth(baseUrl, topic);
console.log(`[Api] Checking auth for ${url}`);
const response = await fetch(url, {
headers: maybeWithAuth({}, user),
});
2022-02-26 07:40:03 +13:00
if (response.status >= 200 && response.status <= 299) {
return true;
}
if (response.status === 401 || response.status === 403) {
// See server/server.go
return false;
}
throw new Error(`Unexpected server response ${response.status}`);
2023-05-24 07:13:01 +12:00
}
/**
* @returns {Promise<{ public_key: string } | undefined>}
*/
async getWebPushConfig(baseUrl) {
const response = await fetch(webPushConfigUrl(baseUrl));
if (response.ok) {
return response.json();
}
if (response.status === 404) {
// web push is not enabled
return undefined;
}
throw new Error(`Unexpected server response ${response.status}`);
}
async subscribeWebPush(baseUrl, topic, browserSubscription) {
const user = await userManager.get(baseUrl);
const url = topicUrlWebPushSubscribe(baseUrl, topic);
console.log(`[Api] Sending Web Push Subscription ${url}`);
const response = await fetch(url, {
method: "POST",
headers: maybeWithAuth({}, user),
body: JSON.stringify({ browser_subscription: browserSubscription }),
});
if (response.ok) {
return true;
}
throw new Error(`Unexpected server response ${response.status}`);
}
async unsubscribeWebPush(subscription) {
const user = await userManager.get(subscription.baseUrl);
const url = topicUrlWebPushUnsubscribe(subscription.baseUrl, subscription.topic);
console.log(`[Api] Unsubscribing Web Push Subscription ${url}`);
const response = await fetch(url, {
method: "POST",
headers: maybeWithAuth({}, user),
body: JSON.stringify({ endpoint: subscription.webPushEndpoint }),
});
if (response.ok) {
return true;
}
throw new Error(`Unexpected server response ${response.status}`);
}
2022-12-25 06:10:51 +13:00
}
const api = new Api();
export default api;