diff --git a/client/components/Settings/Account.vue b/client/components/Settings/Account.vue index 14e0c966..68146c11 100644 --- a/client/components/Settings/Account.vue +++ b/client/components/Settings/Account.vue @@ -174,7 +174,8 @@ export default defineComponent({ } socket.once("change-password", (response) => { - passwordChangeStatus.value = response; + // TODO type + passwordChangeStatus.value = response as any; }); socket.emit("change-password", data); diff --git a/client/components/Windows/NetworkEdit.vue b/client/components/Windows/NetworkEdit.vue index 69bd296d..5bc487f3 100644 --- a/client/components/Windows/NetworkEdit.vue +++ b/client/components/Windows/NetworkEdit.vue @@ -28,7 +28,7 @@ export default defineComponent({ const networkData = ref(null); const setNetworkData = () => { - socket.emit("network:get", route.params.uuid); + socket.emit("network:get", String(route.params.uuid)); networkData.value = store.getters.findNetwork(route.params.uuid as string); }; @@ -43,13 +43,6 @@ export default defineComponent({ switchToChannel(network.channels[0]); }; - // TODO: verify we dont need to watch uuid specifically - // was: - // watch: { - // "$route.params.uuid"() { - // this.setNetworkData(); - // }, - // }, watch( () => route.params.uuid, (newValue) => { diff --git a/client/components/Windows/SearchResults.vue b/client/components/Windows/SearchResults.vue index 0e54d69c..e662f8bd 100644 --- a/client/components/Windows/SearchResults.vue +++ b/client/components/Windows/SearchResults.vue @@ -199,7 +199,7 @@ export default defineComponent({ socket.emit("search", { networkUuid: network.value?.uuid, channelName: channel.value?.name, - searchTerm: route.query.q, + searchTerm: String(route.query.q), offset: offset.value, }); }; @@ -218,7 +218,7 @@ export default defineComponent({ socket.emit("search", { networkUuid: network.value?.uuid, channelName: channel.value?.name, - searchTerm: route.query.q, + searchTerm: String(route.query.q), offset: offset.value + 1, }); }; diff --git a/client/js/constants.ts b/client/js/constants.ts index ccbb4465..e45decf4 100644 --- a/client/js/constants.ts +++ b/client/js/constants.ts @@ -28,7 +28,7 @@ const timeFormats = { export default { colorCodeMap, - commands: [], + commands: [] as string[], condensedTypes, timeFormats, // Same value as media query in CSS that forces sidebars to become overlays diff --git a/client/js/socket-events/auth.ts b/client/js/socket-events/auth.ts index 1b4ce2fd..da37333a 100644 --- a/client/js/socket-events/auth.ts +++ b/client/js/socket-events/auth.ts @@ -3,7 +3,7 @@ import storage from "../localStorage"; import {router, navigate} from "../router"; import {store} from "../store"; import location from "../location"; -let lastServerHash = null; +let lastServerHash: string | null = null; declare global { interface Window { diff --git a/client/js/socket-events/changelog.ts b/client/js/socket-events/changelog.ts index 74af9a63..09aaaadb 100644 --- a/client/js/socket-events/changelog.ts +++ b/client/js/socket-events/changelog.ts @@ -2,6 +2,8 @@ import socket from "../socket"; import {store} from "../store"; socket.on("changelog", function (data) { + // TODO + // @ts-ignore store.commit("versionData", data); store.commit("versionDataExpired", false); diff --git a/client/js/socket-events/init.ts b/client/js/socket-events/init.ts index ee5b95b2..0b2ed7c4 100644 --- a/client/js/socket-events/init.ts +++ b/client/js/socket-events/init.ts @@ -24,7 +24,7 @@ socket.on("init", function (data) { window.g_TheLoungeRemoveLoading(); } - nextTick(() => { + void nextTick(() => { // If we handled query parameters like irc:// links or just general // connect parameters in public mode, then nothing to do here if (!handleQueryParams()) { diff --git a/client/js/socket.ts b/client/js/socket.ts index ac08346b..4fdb0ec2 100644 --- a/client/js/socket.ts +++ b/client/js/socket.ts @@ -1,6 +1,7 @@ import io, {Socket} from "socket.io-client"; +import type {ServerToClientEvents, ClientToServerEvents} from "../../src/types/socket-events"; -const socket: Socket = io({ +const socket: Socket = io({ transports: JSON.parse(document.body.dataset.transports || "['polling', 'websocket']"), path: window.location.pathname + "socket.io/", autoConnect: false, diff --git a/client/js/store.ts b/client/js/store.ts index f35fdd2a..0908a04a 100644 --- a/client/js/store.ts +++ b/client/js/store.ts @@ -63,19 +63,22 @@ export type State = { sidebarOpen: boolean; sidebarDragging: boolean; userlistOpen: boolean; - versionData: null | { - latest: { - version: string; - prerelease: boolean; - url: string; - }; - current: { - version: string; - prerelease: boolean; - url: string; - changelog: string; - }; - }; + versionData: + | null + | undefined + | { + latest: { + version: string; + prerelease: boolean; + url: string; + }; + current: { + version: string; + prerelease: boolean; + url: string; + changelog: string; + }; + }; versionStatus: "loading" | "new-version" | "new-packages" | "up-to-date" | "error"; versionDataExpired: boolean; serverHasSettings: boolean; @@ -388,8 +391,6 @@ const storePattern = { getters, }; -const settingsStore = createSettingsStore(store); - // https://vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function export const key: InjectionKey> = Symbol(); @@ -404,6 +405,8 @@ export type TypedStore = Omit, "getters" | "commit"> & { export const store = createStore(storePattern) as TypedStore; +const settingsStore = createSettingsStore(store); + // Settings module is registered dynamically because it benefits // from a direct reference to the store store.registerModule("settings", settingsStore); diff --git a/client/js/upload.ts b/client/js/upload.ts index 5feb7c51..d6b32900 100644 --- a/client/js/upload.ts +++ b/client/js/upload.ts @@ -279,15 +279,11 @@ class Uploader { const initStart = textbox.selectionStart; - if (!initStart) { - throw new Error("Could not find selection start in textbox in upload"); - } - // Get the text before the cursor, and add a space if it's not in the beginning - const headToCursor = initStart > 0 ? textbox.value.substr(0, initStart) + " " : ""; + const headToCursor = initStart > 0 ? textbox.value.substring(0, initStart) + " " : ""; // Get the remaining text after the cursor - const cursorToTail = textbox.value.substr(initStart); + const cursorToTail = textbox.value.substring(initStart); // Construct the value until the point where we want the cursor to be const textBeforeTail = headToCursor + fullURL + " "; diff --git a/client/tsconfig.json b/client/tsconfig.json index 33b9d8b3..417b1905 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -5,6 +5,7 @@ ] /* Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later. */, "files": [ "../package.json", + "../src/types/socket-events.d.ts", "./js/helpers/fullnamemap.json", "./js/helpers/simplemap.json" ] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */, diff --git a/src/client.ts b/src/client.ts index 4d93d70f..29fe7f73 100644 --- a/src/client.ts +++ b/src/client.ts @@ -17,7 +17,7 @@ import SqliteMessageStorage from "./plugins/messageStorage/sqlite"; import TextFileMessageStorage from "./plugins/messageStorage/text"; import Network, {IgnoreListItem, NetworkWithIrcFramework} from "./models/network"; import ClientManager from "./clientManager"; -import {MessageStorage, SearchQuery, SearchResponse} from "./plugins/messageStorage/types"; +import {MessageStorage, SearchQuery} from "./plugins/messageStorage/types"; const events = [ "away", diff --git a/src/plugins/changelog.ts b/src/plugins/changelog.ts index 6c6ed069..3f9f50c6 100644 --- a/src/plugins/changelog.ts +++ b/src/plugins/changelog.ts @@ -11,16 +11,7 @@ export default { fetch, checkForUpdates, }; - -const versions = { - current: { - version: `v${pkg.version}`, - changelog: undefined, - }, - expiresAt: -1, - latest: undefined, - packages: undefined, -} as { +export type ChangelogData = { current: { version: string; changelog?: string; @@ -34,6 +25,16 @@ const versions = { packages?: boolean; }; +const versions = { + current: { + version: `v${pkg.version}`, + changelog: undefined, + }, + expiresAt: -1, + latest: undefined, + packages: undefined, +} as ChangelogData; + async function fetch() { const time = Date.now(); diff --git a/src/plugins/irc-events/message.ts b/src/plugins/irc-events/message.ts index 2db8add2..c00e742c 100644 --- a/src/plugins/irc-events/message.ts +++ b/src/plugins/irc-events/message.ts @@ -31,7 +31,7 @@ export default function (irc, network) { handleMessage(data); }); - function handleMessage(data) { + function handleMessage(data: any) { let chan; let from; let highlight = false; diff --git a/src/plugins/messageStorage/sqlite.ts b/src/plugins/messageStorage/sqlite.ts index 53988fcc..d0bfa7e8 100644 --- a/src/plugins/messageStorage/sqlite.ts +++ b/src/plugins/messageStorage/sqlite.ts @@ -7,9 +7,12 @@ import Config from "../../config"; import Msg, {Message} from "../../models/msg"; import Client from "../../client"; import Chan, {Channel} from "../../models/chan"; -import type {SearchResponse, SqliteMessageStorage as ISqliteMessageStorage} from "./types"; +import type { + SearchResponse, + SearchQuery, + SqliteMessageStorage as ISqliteMessageStorage, +} from "./types"; import Network from "../../models/network"; -import {SearchQuery} from "./types"; // TODO; type let sqlite3: any; @@ -69,7 +72,7 @@ class SqliteMessageStorage implements ISqliteMessageStorage { "SELECT value FROM options WHERE name = 'schema_version'", (err, row) => { if (err) { - return log.error(`Failed to retrieve schema version: ${err}`); + return log.error(`Failed to retrieve schema version: ${err.toString()}`); } // New table diff --git a/src/server.ts b/src/server.ts index 0ee0af2b..ad17c4cb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -27,6 +27,12 @@ import packages from "./plugins/packages/index"; import {NetworkWithIrcFramework} from "./models/network"; import {ChanType} from "./models/chan"; import Utils from "./command-line/utils"; +import type { + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents, + SocketData, +} from "./types/socket-events"; type ServerOptions = { dev: boolean; @@ -212,7 +218,12 @@ export default async function ( return; } - const sockets = new Server(server, { + const sockets = new Server< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents, + SocketData + >(server, { wsEngine: wsServer, cookie: false, serveClient: false, diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 09595928..6ca0976a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1 +1,2 @@ import "./modules"; +import "./socket-events"; diff --git a/src/types/socket-events.d.ts b/src/types/socket-events.d.ts new file mode 100644 index 00000000..49135693 --- /dev/null +++ b/src/types/socket-events.d.ts @@ -0,0 +1,143 @@ +import {ClientNetwork} from "../../client/js/types"; +import Msg from "../models/msg"; +import {ChangelogData} from "../plugins/changelog"; +import {ClientConfiguration} from "../server"; + +type Session = { + current: boolean; + active: number; + lastUse: number; + ip: string; + agent: string; + token: string; +}; + +interface ServerToClientEvents { + "auth:failed": () => void; + "auth:start": (serverHash: number) => void; + "auth:success": () => void; + + "upload:auth": (token: string) => void; + + changelog: (data: ChangelogData) => void; + "changelog:newversion": () => void; + + "change-password": ({success, error}: {success: boolean; error?: any}) => void; + + commands: (data: string[]) => void; + + configuration: (config: ClientConfiguration) => void; + + "push:issubscribed": (isSubscribed: boolean) => void; + "push:unregister": () => void; + + "sessions:list": (data: Session[]) => void; + + more: ({ + chan, + messages, + totalMessages, + }: { + chan: number; + messages: Msg[]; + totalMessages: number; + }) => void; + + "msg:preview": ({id, chan, preview}: {id: number; chan: number; preview: string}) => void; + + init: ({ + active, + networks, + token, + }: { + active: number; + networks: ClientNetwork[]; + token: string; + }) => void; +} + +interface ClientToServerEvents { + "auth:perform": ({user: string, password: string}) => void; + + changelog: () => void; + + "change-password": ({ + old_password: string, + new_password: string, + verify_password: string, + }) => void; + + open: (channelId: number) => void; + + names: ({target: number}) => void; + + input: ({target, text}: {target: number; text: string}) => void; + + "upload:auth": () => void; + "upload:ping": (token: string) => void; + + "history:clear": ({target}: {target: number}) => void; + + "mute:change": ({target, setMutedTo}: {target: number; setMutedTo: boolean}) => void; + + "push:register": (subscriptionJson: PushSubscriptionJSON) => void; + "push:unregister": () => void; + + "setting:get": () => void; + "setting:set": ({name: string, value: any}) => void; + + "sessions:get": () => void; + + sort: ({type, order}: {type: string; order: any; target?: string}) => void; + + "mentions:dismiss": (msgId: number) => void; + "mentions:dismiss_all": () => void; + "mentions:get": () => void; + + more: ({ + target, + lastId, + condensed, + }: { + target: number; + lastId: number; + condensed: boolean; + }) => void; + + "msg:preview:toggle": ({ + target, + messageIds, + shown, + }: { + target: number; + messageIds: number[]; + shown: boolean; + }) => void; + + "network:get": (uuid: string) => void; + "network:edit": (data: Record) => void; + "network:new": (data: Record) => void; + + "sign-out": (token?: string) => void; + + search: ({ + networkUuid, + channelName, + searchTerm, + offset, + }: { + networkUuid?: string; + channelName?: string; + searchTerm?: string; + offset: number; + }) => void; +} + +interface InterServerEvents { + ping: () => void; +} + +interface SocketData { + name: string; + age: number; +}