Handle promise linting (#5488)

This commit is contained in:
Tom Moor
2023-06-28 20:18:18 -04:00
committed by GitHub
parent f3d8129a13
commit 89d5527d39
101 changed files with 395 additions and 343 deletions

View File

@@ -3,6 +3,7 @@
"parserOptions": {
"sourceType": "module",
"extraFileExtensions": [".json"],
"project": "./tsconfig.json",
"ecmaFeatures": {
"jsx": true
}
@@ -36,6 +37,14 @@
"component": true,
"html": true
}],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksVoidReturn": false
}
],
"@typescript-eslint/no-unused-vars": [
"error",
{

View File

@@ -124,13 +124,13 @@ export const starCollection = createAction({
stores.policies.abilities(activeCollectionId).star
);
},
perform: ({ activeCollectionId, stores }) => {
perform: async ({ activeCollectionId, stores }) => {
if (!activeCollectionId) {
return;
}
const collection = stores.collections.get(activeCollectionId);
collection?.star();
await collection?.star();
},
});
@@ -150,13 +150,13 @@ export const unstarCollection = createAction({
stores.policies.abilities(activeCollectionId).unstar
);
},
perform: ({ activeCollectionId, stores }) => {
perform: async ({ activeCollectionId, stores }) => {
if (!activeCollectionId) {
return;
}
const collection = stores.collections.get(activeCollectionId);
collection?.unstar();
await collection?.unstar();
},
});

View File

@@ -98,13 +98,13 @@ export const starDocument = createAction({
!document?.isStarred && stores.policies.abilities(activeDocumentId).star
);
},
perform: ({ activeDocumentId, stores }) => {
perform: async ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.star();
await document?.star();
},
});
@@ -124,13 +124,13 @@ export const unstarDocument = createAction({
stores.policies.abilities(activeDocumentId).unstar
);
},
perform: ({ activeDocumentId, stores }) => {
perform: async ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.unstar();
await document?.unstar();
},
});
@@ -186,14 +186,14 @@ export const unpublishDocument = createAction({
}
return stores.policies.abilities(activeDocumentId).unpublish;
},
perform: ({ activeDocumentId, stores, t }) => {
perform: async ({ activeDocumentId, stores, t }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.unpublish();
await document?.unpublish();
stores.toasts.showToast(t("Document unpublished"), {
type: "success",
@@ -218,14 +218,14 @@ export const subscribeDocument = createAction({
stores.policies.abilities(activeDocumentId).subscribe
);
},
perform: ({ activeDocumentId, stores, t }) => {
perform: async ({ activeDocumentId, stores, t }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.subscribe();
await document?.subscribe();
stores.toasts.showToast(t("Subscribed to document notifications"), {
type: "success",
@@ -250,14 +250,14 @@ export const unsubscribeDocument = createAction({
stores.policies.abilities(activeDocumentId).unsubscribe
);
},
perform: ({ activeDocumentId, stores, currentUserId, t }) => {
perform: async ({ activeDocumentId, stores, currentUserId, t }) => {
if (!activeDocumentId || !currentUserId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.unsubscribe(currentUserId);
await document?.unsubscribe(currentUserId);
stores.toasts.showToast(t("Unsubscribed from document notifications"), {
type: "success",
@@ -274,13 +274,13 @@ export const downloadDocumentAsHTML = createAction({
iconInContextMenu: false,
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).download,
perform: ({ activeDocumentId, stores }) => {
perform: async ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.download(ExportContentType.Html);
await document?.download(ExportContentType.Html);
},
});
@@ -321,13 +321,13 @@ export const downloadDocumentAsMarkdown = createAction({
iconInContextMenu: false,
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).download,
perform: ({ activeDocumentId, stores }) => {
perform: async ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.download(ExportContentType.Markdown);
await document?.download(ExportContentType.Markdown);
},
});

View File

@@ -18,7 +18,7 @@ const Authenticated = ({ children }: Props) => {
// Watching for language changes here as this is the earliest point we have
// the user available and means we can start loading translations faster
React.useEffect(() => {
changeLanguage(language, i18n);
void changeLanguage(language, i18n);
}, [i18n, language]);
if (auth.authenticated) {
@@ -31,7 +31,7 @@ const Authenticated = ({ children }: Props) => {
return children;
}
auth.logout(true);
void auth.logout(true);
return <Redirect to="/" />;
};

View File

@@ -57,7 +57,7 @@ function Collaborators(props: Props) {
if (!isEqual(requestedUserIds, ids) && ids.length > 0) {
setRequestedUserIds(ids);
users.fetchPage({ ids, limit: 100 });
void users.fetchPage({ ids, limit: 100 });
}
}, [document, users, presentIds, document.collaboratorIds, requestedUserIds]);

View File

@@ -71,9 +71,9 @@ function CollectionDescription({ collection }: Props) {
);
const handleChange = React.useCallback(
(getValue) => {
async (getValue) => {
setDirty(true);
handleSave(getValue);
await handleSave(getValue);
},
[handleSave]
);

View File

@@ -38,13 +38,13 @@ const MenuItem = (
ref: React.Ref<HTMLAnchorElement>
) => {
const handleClick = React.useCallback(
(ev) => {
async (ev) => {
hide?.();
if (onClick) {
ev.preventDefault();
onClick(ev);
await onClick(ev);
}
hide?.();
},
[onClick, hide]
);

View File

@@ -28,7 +28,7 @@ const DefaultCollectionInputSelect = ({
const { showToast } = useToasts();
React.useEffect(() => {
async function load() {
async function fetchData() {
if (!collections.isLoaded && !fetching && !fetchError) {
try {
setFetching(true);
@@ -48,7 +48,7 @@ const DefaultCollectionInputSelect = ({
}
}
}
load();
void fetchData();
}, [showToast, fetchError, t, fetching, collections]);
const options = React.useMemo(

View File

@@ -30,7 +30,7 @@ export default function DesktopEventHandler() {
action: {
text: "Install now",
onClick: () => {
Desktop.bridge?.restartAndInstall();
void Desktop.bridge?.restartAndInstall();
},
},
});

View File

@@ -58,10 +58,10 @@ function DocumentCard(props: Props) {
};
const handleUnpin = React.useCallback(
(ev) => {
async (ev) => {
ev.preventDefault();
ev.stopPropagation();
pin?.delete();
await pin?.delete();
},
[pin]
);

View File

@@ -164,7 +164,7 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
(collection) => expandedNodes.includes(collection.id) || searchTerm
)
.forEach((collection) => {
collection.fetchDocuments();
void collection.fetchDocuments();
});
}, [collections, expandedNodes, searchTerm]);

View File

@@ -51,9 +51,9 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
ref.current?.focus();
};
const prefetchRevision = () => {
const prefetchRevision = async () => {
if (event.name === "revisions.create" && event.modelId) {
revisions.fetch(event.modelId);
await revisions.fetch(event.modelId);
}
};

View File

@@ -7,7 +7,6 @@ import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
import { isExternalUrl } from "@shared/utils/urls";
import HoverPreviewDocument from "~/components/HoverPreviewDocument";
import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { fadeAndSlideDown } from "~/styles/animations";
const DELAY_OPEN = 300;
@@ -23,7 +22,6 @@ type Props = {
};
function HoverPreviewInternal({ element, id, onClose }: Props) {
const { documents } = useStores();
const slug = parseDocumentSlug(element.href);
const [isVisible, setVisible] = React.useState(false);
const timerClose = React.useRef<ReturnType<typeof setTimeout>>();
@@ -57,10 +55,6 @@ function HoverPreviewInternal({ element, id, onClose }: Props) {
};
React.useEffect(() => {
if (slug) {
documents.prefetchDocument(slug);
}
startOpenTimer();
if (cardRef.current) {

View File

@@ -20,9 +20,11 @@ function HoverPreviewDocument({ url, id, children }: Props) {
const { documents } = useStores();
const slug = parseDocumentSlug(url);
if (slug) {
documents.prefetchDocument(slug);
}
React.useEffect(() => {
if (slug) {
void documents.prefetchDocument(slug);
}
}, [documents, slug]);
const document = slug ? documents.getByUrl(slug) : undefined;
if (!document || document.id === id) {

View File

@@ -108,11 +108,7 @@ const InputSelect = (props: Props) => {
}
previousValue.current = select.selectedValue;
async function load() {
await onChange?.(select.selectedValue);
}
load();
onChange?.(select.selectedValue);
}, [onChange, select.selectedValue]);
React.useLayoutEffect(() => {

View File

@@ -73,11 +73,11 @@ export default function LanguagePrompt() {
</Trans>
<br />
<Link
onClick={() => {
auth.updateUser({
onClick={async () => {
ui.setLanguagePromptDismissed();
await auth.updateUser({
language,
});
ui.setLanguagePromptDismissed();
}}
>
{t("Change Language")}

View File

@@ -1,4 +1,5 @@
import * as React from "react";
import Logger from "~/utils/Logger";
import { loadPolyfills } from "~/utils/polyfills";
/**
@@ -8,9 +9,13 @@ export const LazyPolyfill: React.FC = ({ children }) => {
const [isLoaded, setIsLoaded] = React.useState(false);
React.useEffect(() => {
loadPolyfills().then(() => {
setIsLoaded(true);
});
loadPolyfills()
.then(() => {
setIsLoaded(true);
})
.catch((error) => {
Logger.error("Polyfills failed to load", error);
});
}, []);
if (!isLoaded) {

View File

@@ -30,11 +30,11 @@ function NotificationListItem({ notification, onNavigate }: Props) {
if (event.altKey) {
event.preventDefault();
event.stopPropagation();
notification.toggleRead();
void notification.toggleRead();
return;
}
notification.markAsRead();
void notification.markAsRead();
onNavigate();
};

View File

@@ -43,7 +43,9 @@ function Notifications(
// Account for old versions of the desktop app that don't have the
// setNotificationCount method on the bridge.
if (Desktop.bridge && "setNotificationCount" in Desktop.bridge) {
Desktop.bridge.setNotificationCount(notifications.approximateUnreadCount);
void Desktop.bridge.setNotificationCount(
notifications.approximateUnreadCount
);
}
}, [notifications.approximateUnreadCount]);

View File

@@ -70,7 +70,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
allowLoadMore = true;
componentDidMount() {
this.fetchResults();
void this.fetchResults();
}
componentDidUpdate(prevProps: Props<T>) {
@@ -79,7 +79,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
!isEqual(prevProps.options, this.props.options)
) {
this.reset();
this.fetchResults();
void this.fetchResults();
}
}

View File

@@ -11,7 +11,7 @@ export default function SearchActions() {
React.useEffect(() => {
if (!searches.isLoaded) {
searches.fetchPage({});
void searches.fetchPage({});
}
}, [searches]);

View File

@@ -43,8 +43,8 @@ function AppSidebar() {
React.useEffect(() => {
if (!user.isViewer) {
documents.fetchDrafts();
documents.fetchTemplates();
void documents.fetchDrafts();
void documents.fetchTemplates();
}
}, [documents, user.isViewer]);

View File

@@ -57,7 +57,7 @@ function CollectionLinkChildren({
if (!collection) {
return;
}
documents.move(item.id, collection.id, undefined, 0);
void documents.move(item.id, collection.id, undefined, 0);
},
collect: (monitor) => ({
isOverReorder: !!monitor.isOver(),

View File

@@ -36,7 +36,7 @@ function Collections() {
] = useDrop({
accept: "collection",
drop: async (item: DragObject) => {
collections.move(
void collections.move(
item.id,
fractionalIndex(null, orderedCollections[0].index)
);

View File

@@ -67,7 +67,7 @@ function InnerDocumentLink(
React.useEffect(() => {
if (isActiveDocument && hasChildDocuments) {
fetchChildDocuments(node.id);
void fetchChildDocuments(node.id);
}
}, [fetchChildDocuments, node, hasChildDocuments, isActiveDocument]);
@@ -115,7 +115,7 @@ function InnerDocumentLink(
);
const handlePrefetch = React.useCallback(() => {
prefetchDocument?.(node.id);
void prefetchDocument?.(node.id);
}, [prefetchDocument, node]);
const handleTitleChange = React.useCallback(
@@ -242,11 +242,11 @@ function InnerDocumentLink(
}
if (expanded) {
documents.move(item.id, collection.id, node.id, 0);
void documents.move(item.id, collection.id, node.id, 0);
return;
}
documents.move(item.id, collection.id, parentId, index + 1);
void documents.move(item.id, collection.id, parentId, index + 1);
},
collect: (monitor) => ({
isOverReorder: monitor.isOver(),

View File

@@ -51,7 +51,7 @@ function DraggableCollectionLink({
] = useDrop({
accept: "collection",
drop: (item: DragObject) => {
collections.move(
void collections.move(
item.id,
fractionalIndex(collection.index, belowCollectionIndex)
);

View File

@@ -40,7 +40,7 @@ function Starred() {
);
React.useEffect(() => {
fetchResults();
void fetchResults();
}, []);
const handleShowMore = async () => {
@@ -52,7 +52,7 @@ function Starred() {
const [{ isOverReorder, isDraggingAnyStar }, dropToReorder] = useDrop({
accept: "star",
drop: async (item: { star: Star }) => {
item.star.save({
void item.star.save({
index: fractionalIndex(null, stars.orderedData[0].index),
});
},

View File

@@ -90,13 +90,9 @@ function StarredLink({ star }: Props) {
}, [star.collectionId, ui.activeCollectionId, locationStateStarred]);
useEffect(() => {
async function load() {
if (documentId) {
await documents.fetch(documentId);
}
if (documentId) {
void documents.fetch(documentId);
}
load();
}, [documentId, documents]);
const handleDisclosureClick = React.useCallback(
@@ -134,7 +130,7 @@ function StarredLink({ star }: Props) {
drop: (item: { star: Star }) => {
const next = star?.next();
item.star.save({
void item.star.save({
index: fractionalIndex(star?.index || null, next?.index || null),
});
},

View File

@@ -36,7 +36,7 @@ export default function Version() {
}
}
loadReleases();
void loadReleases();
}, []);
return (

View File

@@ -380,9 +380,9 @@ class WebsocketProvider extends React.Component<Props> {
// if the user is us then we go ahead and load the collection from API.
this.socket.on(
"collections.add_user",
action((event: WebsocketCollectionUserEvent) => {
async (event: WebsocketCollectionUserEvent) => {
if (auth.user && event.userId === auth.user.id) {
collections.fetch(event.collectionId, {
await collections.fetch(event.collectionId, {
force: true,
});
}
@@ -391,7 +391,7 @@ class WebsocketProvider extends React.Component<Props> {
documents.inCollection(event.collectionId).forEach((document) => {
policies.remove(document.id);
});
})
}
);
// received when a user is removed from having access to a collection

View File

@@ -139,7 +139,7 @@ class LinkEditor extends React.Component<Props, State> {
if (result) {
this.save(result.url, result.title);
} else if (onCreateLink && selectedIndex === results.length) {
this.handleCreateLink(this.suggestedLinkTitle);
void this.handleCreateLink(this.suggestedLinkTitle);
}
} else {
// saves the raw input as href
@@ -376,8 +376,8 @@ class LinkEditor extends React.Component<Props, State> {
subtitle={dictionary.createNewDoc}
icon={<PlusIcon />}
onPointerMove={() => this.handleFocusLink(results.length)}
onClick={() => {
this.handleCreateLink(suggestedLinkTitle);
onClick={async () => {
await this.handleCreateLink(suggestedLinkTitle);
if (this.initialSelectionLength) {
this.moveSelectionToEnd();

View File

@@ -22,7 +22,7 @@ function LinkSearchResult({
const ref = React.useCallback(
(node: HTMLElement | null) => {
if (selected && node) {
scrollIntoView(node, {
void scrollIntoView(node, {
scrollMode: "if-needed",
block: "center",
boundary: (parent) =>

View File

@@ -82,7 +82,7 @@ export default function LinkToolbar({
)
);
createAndInsertLink(view, title, href, {
return createAndInsertLink(view, title, href, {
onCreateLink,
onShowToast: showToast,
dictionary,

View File

@@ -54,7 +54,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
React.useEffect(() => {
if (isActive) {
request();
void request();
}
}, [request, isActive]);

View File

@@ -159,7 +159,7 @@ export default function SelectionToolbar(props: Props) {
.addMark(from, to, markType.create({ href }))
);
createAndInsertLink(view, title, href, {
return createAndInsertLink(view, title, href, {
onCreateLink,
onShowToast,
dictionary,

View File

@@ -25,7 +25,7 @@ function SuggestionsMenuItem({
const ref = React.useCallback(
(node) => {
if (selected && node) {
scrollIntoView(node, {
void scrollIntoView(node, {
scrollMode: "if-needed",
block: "nearest",
boundary: (parent) =>

View File

@@ -233,7 +233,7 @@ export class Editor extends React.PureComponent<
window.addEventListener("theme-changed", this.dispatchThemeChanged);
if (this.props.scrollTo) {
this.scrollToAnchor(this.props.scrollTo);
void this.scrollToAnchor(this.props.scrollTo);
}
this.calculateDir();
@@ -263,7 +263,7 @@ export class Editor extends React.PureComponent<
}
if (this.props.scrollTo && this.props.scrollTo !== prevProps.scrollTo) {
this.scrollToAnchor(this.props.scrollTo);
void this.scrollToAnchor(this.props.scrollTo);
}
// Focus at the end of the document if switching from readOnly and autoFocus

View File

@@ -7,7 +7,7 @@ export const useDesktopTitlebar = () => {
return;
}
const handleDoubleClick = (event: MouseEvent) => {
const handleDoubleClick = async (event: MouseEvent) => {
// Ignore double clicks on interactive elements such as inputs and buttons
if (event.composedPath().some(elementIsInteractive)) {
return;
@@ -19,7 +19,7 @@ export const useDesktopTitlebar = () => {
}
event.preventDefault();
Desktop.bridge.onTitlebarDoubleClick();
await Desktop.bridge.onTitlebarDoubleClick();
};
window.addEventListener("dblclick", handleDoubleClick);

View File

@@ -29,7 +29,7 @@ export default function useEmbeds(loadIfMissing = false) {
}
if (!integrations.isLoaded && !integrations.isFetching && loadIfMissing) {
fetchEmbedIntegrations();
void fetchEmbedIntegrations();
}
}, [integrations, loadIfMissing]);

View File

@@ -100,7 +100,7 @@ function DocumentMenu({
const handleOpen = React.useCallback(async () => {
if (!data && !loading) {
request();
await request();
}
if (onOpen) {
@@ -325,7 +325,7 @@ function DocumentMenu({
checked={document.fullWidth}
onChange={(ev) => {
document.fullWidth = ev.currentTarget.checked;
document.save();
void document.save();
}}
/>
</Style>

View File

@@ -116,9 +116,9 @@ function UserMenu({ user }: Props) {
);
const handleRevoke = React.useCallback(
(ev: React.SyntheticEvent) => {
async (ev: React.SyntheticEvent) => {
ev.preventDefault();
users.delete(user);
await users.delete(user);
},
[users, user]
);
@@ -143,9 +143,9 @@ function UserMenu({ user }: Props) {
);
const handleActivate = React.useCallback(
(ev: React.SyntheticEvent) => {
async (ev: React.SyntheticEvent) => {
ev.preventDefault();
users.activate(user);
await users.activate(user);
},
[users, user]
);

View File

@@ -84,14 +84,14 @@ function CollectionScene() {
setError(undefined);
if (collection) {
pins.fetchPage({
void pins.fetchPage({
collectionId: collection.id,
});
}
}, [pins, collection]);
React.useEffect(() => {
async function load() {
async function fetchData() {
if ((!can || !collection) && !error && !isFetching) {
try {
setError(undefined);
@@ -105,7 +105,7 @@ function CollectionScene() {
}
}
load();
void fetchData();
}, [collections, isFetching, collection, error, id, can]);
useCommandBarActions(

View File

@@ -53,7 +53,7 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
}
};
fetchData();
void fetchData();
}, [
isMobile,
collection.permission,

View File

@@ -36,10 +36,7 @@ function AddGroupsToCollection(props: Props) {
const { t } = useTranslation();
const debouncedFetch = React.useMemo(
() =>
debounce((query) => {
fetchGroups({ query });
}, 250),
() => debounce((query) => fetchGroups({ query }), 250),
[fetchGroups]
);
@@ -47,14 +44,14 @@ function AddGroupsToCollection(props: Props) {
(ev: React.ChangeEvent<HTMLInputElement>) => {
const updatedQuery = ev.target.value;
setQuery(updatedQuery);
debouncedFetch(updatedQuery);
void debouncedFetch(updatedQuery);
},
[debouncedFetch]
);
const handleAddGroup = (group: Group) => {
const handleAddGroup = async (group: Group) => {
try {
collectionGroupMemberships.create({
await collectionGroupMemberships.create({
collectionId: collection.id,
groupId: group.id,
});

View File

@@ -44,9 +44,9 @@ function AddPeopleToCollection({ collection }: Props) {
250
);
const handleAddUser = (user: User) => {
const handleAddUser = async (user: User) => {
try {
memberships.create({
await memberships.create({
collectionId: collection.id,
userId: user.id,
});

View File

@@ -99,7 +99,7 @@ function SharedDocumentScene(props: Props) {
React.useEffect(() => {
if (!auth.user) {
changeLanguage(detectLanguage(), i18n);
void changeLanguage(detectLanguage(), i18n);
}
}, [auth, i18n]);
@@ -125,7 +125,7 @@ function SharedDocumentScene(props: Props) {
setError(err);
}
}
fetchData();
void fetchData();
}, [documents, documentSlug, shareId, ui]);
if (error) {

View File

@@ -69,14 +69,14 @@ function CommentForm({
const { comments } = useStores();
const user = useCurrentUser();
const reset = React.useCallback(() => {
const reset = React.useCallback(async () => {
const isEmpty = editorRef.current?.isEmpty() ?? true;
if (isEmpty && thread?.isNew) {
if (thread.id) {
editor?.removeComment(thread.id);
}
thread.delete();
await thread.delete();
}
}, [editor, thread]);
@@ -173,11 +173,11 @@ function CommentForm({
}
};
const handleCancel = () => {
const handleCancel = async () => {
setData(undefined);
setForceRender((s) => ++s);
setInputFocused(false);
reset();
await reset();
};
const handleFocus = () => {

View File

@@ -114,7 +114,7 @@ function CommentThread({
if (!topRef.current) {
return;
}
scrollIntoView(topRef.current, {
return scrollIntoView(topRef.current, {
scrollMode: "if-needed",
behavior: "smooth",
block: "end",

View File

@@ -86,7 +86,7 @@ function DataLoader({ match, children }: Props) {
setError(err);
}
}
fetchDocument();
void fetchDocument();
}, [ui, documents, document, shareId, documentSlug]);
React.useEffect(() => {
@@ -99,7 +99,7 @@ function DataLoader({ match, children }: Props) {
}
}
}
fetchRevision();
void fetchRevision();
}, [revisions, revisionId]);
React.useEffect(() => {
@@ -112,7 +112,7 @@ function DataLoader({ match, children }: Props) {
}
}
}
fetchRevision();
void fetchRevision();
}, [document, revisionId, revisions]);
React.useEffect(() => {
@@ -128,7 +128,7 @@ function DataLoader({ match, children }: Props) {
}
}
}
fetchSubscription();
void fetchSubscription();
}, [document?.id, subscriptions, revisionId]);
React.useEffect(() => {
@@ -143,7 +143,7 @@ function DataLoader({ match, children }: Props) {
}
}
}
fetchViews();
void fetchViews();
}, [document?.id, document?.isDeleted, revisionId, views]);
const onCreateLink = React.useCallback(
@@ -180,7 +180,7 @@ function DataLoader({ match, children }: Props) {
// when viewing a public share link
if (can.read) {
if (team?.getPreference(TeamPreference.Commenting)) {
comments.fetchDocumentComments(document.id, {
void comments.fetchDocumentComments(document.id, {
limit: 100,
});
}

View File

@@ -128,7 +128,7 @@ class DocumentScene extends React.Component<Props> {
this.props.document.hasEmptyTitle &&
this.props.document.isPersistedOnce
) {
this.props.document.delete();
void this.props.document.delete();
}
}
@@ -167,7 +167,8 @@ class DocumentScene extends React.Component<Props> {
this.props.document.text = template.text;
this.updateIsDirty();
this.onSave({
return this.onSave({
autosave: true,
publish: false,
done: false,
@@ -189,7 +190,7 @@ class DocumentScene extends React.Component<Props> {
});
if (response) {
this.replaceDocument(response.data);
await this.replaceDocument(response.data);
toasts.showToast(t("Document restored"));
history.replace(this.props.document.url, history.location.state);
}
@@ -244,7 +245,7 @@ class DocumentScene extends React.Component<Props> {
}
if (document?.collectionId) {
this.onSave({
void this.onSave({
publish: true,
done: true,
});
@@ -324,12 +325,14 @@ class DocumentScene extends React.Component<Props> {
}
};
autosave = debounce(() => {
this.onSave({
done: false,
autosave: true,
});
}, AUTOSAVE_DELAY);
autosave = debounce(
() =>
this.onSave({
done: false,
autosave: true,
}),
AUTOSAVE_DELAY
);
updateIsDirty = () => {
const { document } = this.props;
@@ -370,7 +373,7 @@ class DocumentScene extends React.Component<Props> {
this.title = value;
this.props.document.title = value;
this.updateIsDirty();
this.autosave();
void this.autosave();
});
goBack = () => {

View File

@@ -220,7 +220,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
React.useEffect(() => {
if (isLocalSynced && isRemoteSynced) {
onSynced?.();
void onSynced?.();
}
}, [onSynced, isLocalSynced, isRemoteSynced]);
@@ -236,14 +236,14 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
!isVisible &&
remoteProvider.status === WebSocketStatus.Connected
) {
remoteProvider.disconnect();
void remoteProvider.disconnect();
}
if (
(!isIdle || isVisible) &&
remoteProvider.status === WebSocketStatus.Disconnected
) {
remoteProvider.connect();
void remoteProvider.connect();
}
}, [remoteProvider, isIdle, isVisible]);

View File

@@ -19,7 +19,7 @@ function References({ document }: Props) {
const location = useLocation();
React.useEffect(() => {
documents.fetchBacklinks(document.id);
void documents.fetchBacklinks(document.id);
}, [documents, document.id]);
const backlinks = documents.getBacklinkedDocuments(document.id);

View File

@@ -76,7 +76,7 @@ function SharePopover({
React.useEffect(() => {
if (visible && team.sharing) {
document.share();
void document.share();
buttonRef.current?.focus();
}

View File

@@ -46,7 +46,7 @@ function DocumentNew() {
}
}
createDocument();
void createDocument();
});
return (

View File

@@ -33,10 +33,7 @@ function AddPeopleToGroup(props: Props) {
const { fetchPage: fetchUsers } = users;
const debouncedFetch = React.useMemo(
() =>
debounce((query) => {
fetchUsers({ query });
}, 250),
() => debounce((query) => fetchUsers({ query }), 250),
[fetchUsers]
);
@@ -44,7 +41,7 @@ function AddPeopleToGroup(props: Props) {
(ev: React.ChangeEvent<HTMLInputElement>) => {
const updatedQuery = ev.target.value;
setQuery(updatedQuery);
debouncedFetch(updatedQuery);
void debouncedFetch(updatedQuery);
},
[debouncedFetch]
);

View File

@@ -29,7 +29,7 @@ function Home() {
const { t } = useTranslation();
React.useEffect(() => {
pins.fetchPage();
void pins.fetchPage();
}, [pins]);
const canManageTeam = usePolicy(team).manage;

View File

@@ -101,7 +101,7 @@ function Login({ children }: Props) {
// Try to detect the user's language and show the login page on its idiom
// if translation is available
React.useEffect(() => {
changeLanguage(detectLanguage(), i18n);
void changeLanguage(detectLanguage(), i18n);
}, [i18n]);
React.useEffect(() => {

View File

@@ -4,7 +4,7 @@ import useStores from "~/hooks/useStores";
const Logout = () => {
const { auth } = useStores();
auth.logout();
void auth.logout();
return <Redirect to="/" />;
};

View File

@@ -94,7 +94,7 @@ class Search extends React.Component<Props> {
handleKeyDown = (ev: React.KeyboardEvent<HTMLInputElement>) => {
if (ev.key === "Enter") {
this.updateLocation(ev.currentTarget.value);
this.fetchResults();
void this.fetchResults();
return;
}
@@ -143,7 +143,7 @@ class Search extends React.Component<Props> {
this.allowLoadMore = true;
// To prevent "no results" showing before debounce kicks in
this.isLoading = true;
this.fetchResults();
void this.fetchResults();
};
handleTermChange = () => {
@@ -153,7 +153,7 @@ class Search extends React.Component<Props> {
this.allowLoadMore = true;
// To prevent "no results" showing before debounce kicks in
this.isLoading = true;
this.fetchResults();
void this.fetchResults();
};
handleFilterChange = (search: {

View File

@@ -18,7 +18,7 @@ function RecentSearches() {
const [isPreloaded] = React.useState(searches.recent.length > 0);
React.useEffect(() => {
searches.fetchPage({});
void searches.fetchPage({});
}, [searches]);
const content = searches.recent.length ? (
@@ -32,9 +32,9 @@ function RecentSearches() {
<Tooltip tooltip={t("Remove search")} delay={150}>
<RemoveButton
aria-label={t("Remove search")}
onClick={(ev) => {
onClick={async (ev) => {
ev.preventDefault();
searchQuery.delete();
await searchQuery.delete();
}}
>
<CloseIcon />

View File

@@ -18,7 +18,7 @@ function UserFilter(props: Props) {
const { users } = useStores();
React.useEffect(() => {
users.fetchPage({
void users.fetchPage({
limit: 100,
});
}, [users]);

View File

@@ -42,7 +42,7 @@ function GoogleAnalytics() {
});
React.useEffect(() => {
integrations.fetchPage({
void integrations.fetchPage({
type: IntegrationType.Analytics,
});
}, [integrations]);

View File

@@ -68,7 +68,7 @@ function Members() {
}
};
fetchData();
void fetchData();
}, [query, sort, filter, page, direction, users, users.counts.all]);
React.useEffect(() => {

View File

@@ -46,7 +46,7 @@ function Security() {
React.useEffect(() => {
if (!providers && !loading) {
request();
void request();
}
}, [loading, providers, request]);

View File

@@ -41,7 +41,7 @@ function SelfHosted() {
});
React.useEffect(() => {
integrations.fetchPage({
void integrations.fetchPage({
type: IntegrationType.Embed,
});
}, [integrations]);

View File

@@ -53,7 +53,7 @@ function Shares() {
}
};
fetchData();
void fetchData();
}, [query, sort, page, direction, shares]);
React.useEffect(() => {

View File

@@ -71,7 +71,7 @@ class ImageUpload extends React.Component<RootStore & Props> {
name: this.file.name,
preset: AttachmentPreset.Avatar,
});
this.props.onSuccess(attachment.url);
void this.props.onSuccess(attachment.url);
} catch (err) {
this.props.onError(err.message);
} finally {

View File

@@ -111,7 +111,7 @@ export default class AuthStore {
// we are signed in and the received data contains no user then sign out
if (this.authenticated) {
if (data.user === null) {
this.logout();
void this.logout();
}
} else {
this.rehydrate(data);
@@ -348,7 +348,7 @@ export default class AuthStore {
this.token = null;
// Tell the host application we logged out, if any allows window cleanup.
Desktop.bridge?.onLogout?.();
void Desktop.bridge?.onLogout?.();
this.rootStore.logout();
try {

View File

@@ -232,8 +232,8 @@ export default class CollectionsStore extends BaseStore<Collection> {
delete = async (collection: Collection) => {
await super.delete(collection);
this.rootStore.documents.fetchRecentlyUpdated();
this.rootStore.documents.fetchRecentlyViewed();
await this.rootStore.documents.fetchRecentlyUpdated();
await this.rootStore.documents.fetchRecentlyViewed();
};
export = (format: FileOperationFormat, includeAttachments: boolean) =>

View File

@@ -179,7 +179,7 @@ export default class UsersStore extends BaseStore<User> {
@action
async delete(user: User, options: Record<string, any> = {}) {
super.delete(user, options);
await super.delete(user, options);
if (!user.isSuspended && user.lastActiveAt) {
this.counts.active -= 1;

View File

@@ -4,7 +4,7 @@ export async function deleteAllDatabases() {
for (const database of databases) {
if (database.name) {
await window.indexedDB.deleteDatabase(database.name);
window.indexedDB.deleteDatabase(database.name);
}
}
}

View File

@@ -15,13 +15,13 @@ describe("i18n env is unset", () => {
it("translation of key should match", () =>
expect(i18n.t("Saving")).toBe("Saving"));
it("translation if changed to de-DE", () => {
i18n.changeLanguage("de-DE");
it("translation if changed to de-DE", async () => {
await i18n.changeLanguage("de-DE");
expect(i18n.t("Saving")).toBe("Speichert");
});
it("translation if changed to pt-PT", () => {
i18n.changeLanguage("pt-PT");
it("translation if changed to pt-PT", async () => {
await i18n.changeLanguage("pt-PT");
expect(i18n.t("Saving")).toBe("A guardar");
});
});
@@ -36,13 +36,13 @@ describe("i18n env is en-US", () => {
it("translation of key should match", () =>
expect(i18n.t("Saving")).toBe("Saving"));
it("translation if changed to de-DE", () => {
i18n.changeLanguage("de-DE");
it("translation if changed to de-DE", async () => {
await i18n.changeLanguage("de-DE");
expect(i18n.t("Saving")).toBe("Speichert");
});
it("translation if changed to pt-PT", () => {
i18n.changeLanguage("pt-PT");
it("translation if changed to pt-PT", async () => {
await i18n.changeLanguage("pt-PT");
expect(i18n.t("Saving")).toBe("A guardar");
});
});
@@ -58,13 +58,13 @@ describe("i18n env is de-DE", () => {
it("translation of key should match", () =>
expect(i18n.t("Saving")).toBe("Speichert"));
it("translation if changed to en-US", () => {
i18n.changeLanguage("en-US");
it("translation if changed to en-US", async () => {
await i18n.changeLanguage("en-US");
expect(i18n.t("Saving")).toBe("Saving");
});
it("translation if changed to pt-PT", () => {
i18n.changeLanguage("pt-PT");
it("translation if changed to pt-PT", async () => {
await i18n.changeLanguage("pt-PT");
expect(i18n.t("Saving")).toBe("A guardar");
});
});
@@ -80,13 +80,13 @@ describe("i18n env is pt-PT", () => {
it("translation of key should match", () =>
expect(i18n.t("Saving")).toBe("A guardar"));
it("translation if changed to en-US", () => {
i18n.changeLanguage("en-US");
it("translation if changed to en-US", async () => {
await i18n.changeLanguage("en-US");
expect(i18n.t("Saving")).toBe("Saving");
});
it("translation if changed to de-DE", () => {
i18n.changeLanguage("de-DE");
it("translation if changed to de-DE", async () => {
await i18n.changeLanguage("de-DE");
expect(i18n.t("Saving")).toBe("Speichert");
});
});

View File

@@ -22,6 +22,7 @@ import backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import { languages } from "@shared/i18n";
import { unicodeCLDRtoBCP47, unicodeBCP47toCLDR } from "@shared/utils/date";
import Logger from "./Logger";
const locales = {
de_DE: de,
@@ -59,11 +60,12 @@ export function dateLocale(language: string | null | undefined) {
*
* @param defaultLanguage The default language to use if the user's language
* is not supported.
* @returns i18n instance
* @returns A promise resolving to the i18n instance
*/
export function initI18n(defaultLanguage = "en_US") {
const lng = unicodeCLDRtoBCP47(defaultLanguage);
i18n
void i18n
.use(backend)
.use(initReactI18next)
.init({
@@ -85,7 +87,11 @@ export function initI18n(defaultLanguage = "en_US") {
supportedLngs: languages.map(unicodeCLDRtoBCP47),
keySeparator: false,
returnNull: false,
})
.catch((err) => {
Logger.error("Failed to initialize i18n", err);
});
return i18n;
}

View File

@@ -8,7 +8,7 @@ export function detectLanguage() {
return `${ln}_${region}`;
}
export function changeLanguage(
export async function changeLanguage(
toLanguageString: string | null | undefined,
i18n: i18n
) {
@@ -19,7 +19,7 @@ export function changeLanguage(
if (locale && i18n.languages?.[0] !== locale) {
// Languages are stored in en_US format in the database, however the
// frontend translation framework (i18next) expects en-US
i18n.changeLanguage(locale);
Desktop.bridge?.setSpellCheckerLanguages(["en-US", locale]);
await i18n.changeLanguage(locale);
await Desktop.bridge?.setSpellCheckerLanguages(["en-US", locale]);
}
}

View File

@@ -30,10 +30,10 @@ function Slack() {
const error = query.get("error");
React.useEffect(() => {
collections.fetchPage({
void collections.fetchPage({
limit: 100,
});
integrations.fetchPage({
void integrations.fetchPage({
limit: 100,
});
}, [collections, integrations]);

View File

@@ -153,6 +153,7 @@ router.post("hooks.slack", async (ctx: APIContext) => {
verifySlackToken(token);
let user, team;
// attempt to find the corresponding team for this request based on the team_id
team = await Team.findOne({
include: [
@@ -299,13 +300,17 @@ router.post("hooks.slack", async (ctx: APIContext) => {
const { results, totalCount } = user
? await SearchHelper.searchForUser(user, text, options)
: await SearchHelper.searchForTeam(team, text, options);
SearchQuery.create({
void SearchQuery.create({
userId: user ? user.id : null,
teamId: team.id,
source: "slack",
query: text,
results: totalCount,
}).catch((err) => {
Logger.error("Failed to create search query", err);
});
const haventSignedIn = t(
`It looks like you havent signed in to {{ appName }} yet, so results may be limited`,
{

View File

@@ -43,7 +43,7 @@ export default async function subscriptionCreator({
// If the subscription was deleted, then just restore the existing row.
if (subscription.deletedAt && resubscribe) {
subscription.restore({ transaction });
await subscription.restore({ transaction });
await Event.create(
{

View File

@@ -41,7 +41,7 @@ export class Mailer {
"SMTP_USERNAME not provided, generating test account…"
);
this.getTestTransportOptions().then((options) => {
void this.getTestTransportOptions().then((options) => {
if (!options) {
Logger.info(
"email",

View File

@@ -53,7 +53,7 @@ async function master() {
await checkPendingMigrations();
if (env.TELEMETRY && env.ENVIRONMENT === "production") {
checkUpdates();
void checkUpdates();
setInterval(checkUpdates, 24 * 3600 * 1000);
}
}
@@ -166,7 +166,7 @@ async function start(id: number, disconnect: () => void) {
process.once("SIGINT", () => ShutdownHelper.execute());
}
throng({
void throng({
master,
worker: start,
count: processCount,

View File

@@ -6,6 +6,7 @@ import winston from "winston";
import env from "@server/env";
import Metrics from "@server/logging/Metrics";
import Sentry from "@server/logging/sentry";
import ShutdownHelper from "@server/utils/ShutdownHelper";
import * as Tracing from "./tracer";
const isProduction = env.ENVIRONMENT === "production";
@@ -162,6 +163,18 @@ class Logger {
}
}
/**
* Report a fatal error and shut down the server
*
* @param message A description of the error
* @param error The error that occurred
* @param extra Arbitrary data to be logged that will appear in prod logs
*/
public fatal(message: string, error: Error, extra?: Extra) {
this.error(message, error, extra);
void ShutdownHelper.execute();
}
/**
* Sanitize data attached to logs and errors to remove sensitive information.
*

View File

@@ -64,7 +64,7 @@ class View extends IdModel {
if (!created) {
model.count += 1;
model.save(options);
await model.save(options);
}
return model;

View File

@@ -174,7 +174,7 @@ This is a new paragraph.
| | | |`,
});
const text = await DocumentHelper.toPlainText(revision);
const text = DocumentHelper.toPlainText(revision);
// Strip all formatting
expect(text).toEqual(`This is a test paragraph

View File

@@ -13,7 +13,7 @@ export default class DebounceProcessor extends BaseProcessor {
async perform(event: Event) {
switch (event.name) {
case "documents.update": {
globalEventQueue.add(
await globalEventQueue.add(
{ ...event, name: "documents.update.delayed" },
{
// speed up revision creation in development, we don't have all the
@@ -41,7 +41,10 @@ export default class DebounceProcessor extends BaseProcessor {
return;
}
globalEventQueue.add({ ...event, name: "documents.update.debounced" });
await globalEventQueue.add({
...event,
name: "documents.update.debounced",
});
break;
}

View File

@@ -44,7 +44,7 @@ export default abstract class ExportDocumentTreeTask extends ExportTask {
let text =
format === FileOperationFormat.HTMLZip
? await DocumentHelper.toHTML(document, { centered: true })
: await DocumentHelper.toMarkdown(document);
: DocumentHelper.toMarkdown(document);
const attachmentIds = includeAttachments
? parseAttachmentIds(document.text)

View File

@@ -135,7 +135,7 @@ export default class ImportJSONTask extends ImportTask {
});
if (Object.values(item.documents).length) {
await mapDocuments(item.documents, collectionId);
mapDocuments(item.documents, collectionId);
}
if (Object.values(item.attachments).length) {

View File

@@ -338,7 +338,7 @@ describe("revisions.create", () => {
enabled: true,
});
subscription.destroy();
await subscription.destroy();
const task = new RevisionCreatedNotificationsTask();

View File

@@ -21,6 +21,7 @@ import {
ValidationError,
IncorrectEditionError,
} from "@server/errors";
import Logger from "@server/logging/Logger";
import auth from "@server/middlewares/authentication";
import { rateLimiter } from "@server/middlewares/rateLimiter";
import validate from "@server/middlewares/validate";
@@ -815,13 +816,15 @@ router.post(
// When requesting subsequent pages of search results we don't want to record
// duplicate search query records
if (offset === 0) {
SearchQuery.create({
void SearchQuery.create({
userId: user?.id,
teamId,
shareId,
source: ctx.state.auth.type || "app", // we'll consider anything that isn't "api" to be "app"
query,
results: totalCount,
}).catch((err) => {
Logger.error("Failed to create search query", err);
});
}

View File

@@ -55,5 +55,5 @@ export default async function main(exit = false) {
} // In the test suite we import the script rather than run via node CLI
if (process.env.NODE_ENV !== "test") {
main(true);
void main(true);
}

View File

@@ -49,5 +49,5 @@ export default async function main(exit = false) {
}
if (process.env.NODE_ENV !== "test") {
main(true);
void main(true);
}

View File

@@ -81,5 +81,5 @@ export default async function main(exit = false) {
}
if (process.env.NODE_ENV !== "test") {
main(true);
void main(true);
}

View File

@@ -67,5 +67,5 @@ export default async function main(exit = false) {
}
if (process.env.NODE_ENV !== "test") {
main(true);
void main(true);
}

View File

@@ -58,5 +58,5 @@ export default async function main(exit = false) {
} // In the test suite we import the script rather than run via node CLI
if (process.env.NODE_ENV !== "test") {
main(true);
void main(true);
}

View File

@@ -48,5 +48,5 @@ export default async function main(exit = false) {
}
if (process.env.NODE_ENV !== "test") {
main(true);
void main(true);
}

View File

@@ -18,7 +18,7 @@ export default function init() {
// Just give everything time to startup before running the first time. Not
// _technically_ required to function.
setTimeout(() => {
run(TaskSchedule.Daily);
run(TaskSchedule.Hourly);
void run(TaskSchedule.Daily);
void run(TaskSchedule.Hourly);
}, 30 * Second);
}

View File

@@ -57,8 +57,8 @@ if (env.CDN_URL) {
defaultSrc.push(env.CDN_URL);
}
export default function init(app: Koa = new Koa(), server?: Server): Koa {
initI18n();
export default function init(app: Koa = new Koa(), server?: Server) {
void initI18n();
if (isProduction) {
// Force redirect to HTTPS protocol unless explicitly disabled

View File

@@ -130,23 +130,27 @@ export default function init(
// Handle events from event queue that should be sent to the clients down ws
const websockets = new WebsocketsProcessor();
websocketQueue.process(
traceFunction({
serviceName: "websockets",
spanName: "process",
isRoot: true,
})(async function (job) {
const event = job.data;
websocketQueue
.process(
traceFunction({
serviceName: "websockets",
spanName: "process",
isRoot: true,
})(async function (job) {
const event = job.data;
Tracing.setResource(`Processor.WebsocketsProcessor`);
Tracing.setResource(`Processor.WebsocketsProcessor`);
websockets.perform(event, io).catch((error) => {
Logger.error("Error processing websocket event", error, {
event,
websockets.perform(event, io).catch((error) => {
Logger.error("Error processing websocket event", error, {
event,
});
});
});
})
);
})
)
.catch((err) => {
Logger.fatal("Error starting websocketQueue", err);
});
}
async function authenticated(io: IO.Server, socket: SocketWithAuth) {
@@ -168,9 +172,6 @@ async function authenticated(io: IO.Server, socket: SocketWithAuth) {
rooms.push(`collection-${collectionId}`)
);
// join all of the rooms at once
socket.join(rooms);
// allow the client to request to join rooms
socket.on("join", async (event) => {
// user is joining a collection channel, because their permissions have
@@ -194,6 +195,9 @@ async function authenticated(io: IO.Server, socket: SocketWithAuth) {
Metrics.increment("websockets.collections.leave");
}
});
// join all of the rooms at once
await socket.join(rooms);
}
/**

View File

@@ -12,128 +12,144 @@ import processors from "../queues/processors";
import tasks from "../queues/tasks";
export default function init() {
initI18n();
void initI18n();
// This queue processes the global event bus
globalEventQueue.process(
traceFunction({
serviceName: "worker",
spanName: "process",
isRoot: true,
})(async function (job) {
const event = job.data;
let err;
globalEventQueue
.process(
traceFunction({
serviceName: "worker",
spanName: "process",
isRoot: true,
})(async function (job) {
const event = job.data;
let err;
setResource(`Event.${event.name}`);
setResource(`Event.${event.name}`);
Logger.info("worker", `Processing ${event.name}`, {
event,
attempt: job.attemptsMade,
});
Logger.info("worker", `Processing ${event.name}`, {
event,
attempt: job.attemptsMade,
});
// For each registered processor we check to see if it wants to handle the
// event (applicableEvents), and if so add a new queued job specifically
// for that processor.
for (const name in processors) {
// For each registered processor we check to see if it wants to handle the
// event (applicableEvents), and if so add a new queued job specifically
// for that processor.
for (const name in processors) {
const ProcessorClass = processors[name];
if (!ProcessorClass) {
throw new Error(
`Received event "${event.name}" for processor (${name}) that isn't registered. Check the file name matches the class name.`
);
}
try {
if (name === "WebsocketsProcessor") {
// websockets are a special case on their own queue because they must
// only be consumed by the websockets service rather than workers.
await websocketQueue.add(job.data);
} else if (
ProcessorClass.applicableEvents.includes(event.name) ||
ProcessorClass.applicableEvents.includes("*")
) {
await processorEventQueue.add({ event, name });
}
} catch (error) {
Logger.error(
`Error adding ${event.name} to ${name} queue`,
error,
event
);
err = error;
}
}
if (err) {
throw err;
}
})
)
.catch((err) => {
Logger.fatal("Error starting globalEventQueue", err);
});
// Jobs for individual processors are processed here. Only applicable events
// as unapplicable events were filtered in the global event queue above.
processorEventQueue
.process(
traceFunction({
serviceName: "worker",
spanName: "process",
isRoot: true,
})(async function (job) {
const { event, name } = job.data;
const ProcessorClass = processors[name];
setResource(`Processor.${name}`);
if (!ProcessorClass) {
throw new Error(
`Received event "${event.name}" for processor (${name}) that isn't registered. Check the file name matches the class name.`
);
}
try {
if (name === "WebsocketsProcessor") {
// websockets are a special case on their own queue because they must
// only be consumed by the websockets service rather than workers.
await websocketQueue.add(job.data);
} else if (
ProcessorClass.applicableEvents.includes(event.name) ||
ProcessorClass.applicableEvents.includes("*")
) {
await processorEventQueue.add({ event, name });
const processor = new ProcessorClass();
if (processor.perform) {
Logger.info("worker", `${name} running ${event.name}`, {
event,
});
try {
await processor.perform(event);
} catch (err) {
Logger.error(
`Error processing ${event.name} in ${name}`,
err,
event
);
throw err;
}
} catch (error) {
Logger.error(
`Error adding ${event.name} to ${name} queue`,
error,
event
);
err = error;
}
}
if (err) {
throw err;
}
})
);
// Jobs for individual processors are processed here. Only applicable events
// as unapplicable events were filtered in the global event queue above.
processorEventQueue.process(
traceFunction({
serviceName: "worker",
spanName: "process",
isRoot: true,
})(async function (job) {
const { event, name } = job.data;
const ProcessorClass = processors[name];
setResource(`Processor.${name}`);
if (!ProcessorClass) {
throw new Error(
`Received event "${event.name}" for processor (${name}) that isn't registered. Check the file name matches the class name.`
);
}
const processor = new ProcessorClass();
if (processor.perform) {
Logger.info("worker", `${name} running ${event.name}`, {
event,
});
try {
await processor.perform(event);
} catch (err) {
Logger.error(`Error processing ${event.name} in ${name}`, err, event);
throw err;
}
}
})
);
})
)
.catch((err) => {
Logger.fatal("Error starting processorEventQueue", err);
});
// Jobs for async tasks are processed here.
taskQueue.process(
traceFunction({
serviceName: "worker",
spanName: "process",
isRoot: true,
})(async function (job) {
const { name, props } = job.data;
const TaskClass = tasks[name];
taskQueue
.process(
traceFunction({
serviceName: "worker",
spanName: "process",
isRoot: true,
})(async function (job) {
const { name, props } = job.data;
const TaskClass = tasks[name];
setResource(`Task.${name}`);
setResource(`Task.${name}`);
if (!TaskClass) {
throw new Error(
`Task "${name}" is not registered. Check the file name matches the class name.`
);
}
if (!TaskClass) {
throw new Error(
`Task "${name}" is not registered. Check the file name matches the class name.`
);
}
Logger.info("worker", `${name} running`, props);
Logger.info("worker", `${name} running`, props);
const task = new TaskClass();
const task = new TaskClass();
try {
await task.perform(props);
} catch (err) {
Logger.error(`Error processing task in ${name}`, err, props);
throw err;
}
})
);
try {
await task.perform(props);
} catch (err) {
Logger.error(`Error processing task in ${name}`, err, props);
throw err;
}
})
)
.catch((err) => {
Logger.fatal("Error starting taskQueue", err);
});
}

View File

@@ -60,7 +60,7 @@ export async function signIn(
await user.updateSignedIn(ctx.request.ip);
// don't await event creation for a faster sign-in
Event.create({
void Event.create({
name: "users.signin",
actorId: user.id,
userId: user.id,

View File

@@ -25,9 +25,10 @@ export function opts(user?: User | null) {
*
* @returns i18n instance
*/
export function initI18n() {
export async function initI18n() {
const lng = unicodeCLDRtoBCP47(env.DEFAULT_LANGUAGE);
i18n.use(backend).init({
i18n.use(backend);
await i18n.init({
compatibilityJSON: "v3",
backend: {
loadPath: (language: string) =>

View File

@@ -62,7 +62,7 @@ function getNewState({
element.classList.remove("diagram-hidden");
}
import("mermaid").then((module) => {
void import("mermaid").then((module) => {
module.default.initialize({
startOnLoad: true,
// TODO: Make dynamic based on the width of the editor or remove in

Some files were not shown because too many files have changed in this diff Show More