lint fixes

This commit is contained in:
2026-04-04 17:41:43 -04:00
parent 9d031c394d
commit 11274c0ce6
29 changed files with 1019 additions and 110 deletions
+6
View File
@@ -54,3 +54,9 @@ src-tauri/gen/
# Ignore this file
.gitignore
# wrangler files
.wrangler
.dev.vars*
!.dev.vars.example
!.env.example
+8
View File
@@ -1,2 +1,10 @@
dist/
node_modules/
.git/
.cursor/
coverage/
spacetimedb/dist/
src/module_bindings/
src-tauri/target/
src-tauri/gen/
pnpm-lock.yaml
+4 -3
View File
@@ -3,7 +3,7 @@ import js from "@eslint/js";
import tseslint from "typescript-eslint";
import svelte from "eslint-plugin-svelte";
import svelteParser from "svelte-eslint-parser";
import { defineConfig } from 'eslint/config';
import { defineConfig } from "eslint/config";
export default defineConfig([
tseslint.configs.recommended,
@@ -32,7 +32,7 @@ export default defineConfig([
"README.md",
".github/",
".cursor/",
"spacetimedb/dist",
"spacetimedb/",
"src/module_bindings/",
"src-tauri/**",
],
@@ -64,13 +64,14 @@ export default defineConfig([
projectService: true,
extraFileExtensions: [".svelte"],
svelteFeatures: {
runes: true
runes: true,
},
},
},
},
{
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"@/no-trailing-spaces": "warn",
"@typescript-eslint/no-unused-vars": [
+7 -3
View File
@@ -9,13 +9,15 @@
"build": "tsc -b && vite build",
"format": "prettier . --write --ignore-path ../../.prettierignore",
"lint": "eslint .",
"preview": "vite preview",
"preview": "pnpm run build && wrangler dev",
"test": "vitest run",
"spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb",
"spacetime:publish:local": "spacetime publish --module-path spacetimedb --server local",
"spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud",
"deploy:local": "docker compose -f docker-compose.local.yml up --build",
"deploy:maincloud": "docker compose -f docker-compose.maincloud.yml up --build"
"deploy:maincloud": "docker compose -f docker-compose.maincloud.yml up --build",
"deploy:cloudflare": "wrangler deploy",
"deploy": "pnpm run build && wrangler deploy"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.2.0",
@@ -27,6 +29,7 @@
"svelte-check": "^4.4.6"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.31.0",
"@eslint/js": "^9.17.0",
"@tauri-apps/cli": "^2.10.1",
"@testing-library/jest-dom": "^6.6.3",
@@ -44,6 +47,7 @@
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^8.0.3",
"vitest": "3.2.4"
"vitest": "3.2.4",
"wrangler": "^4.80.0"
}
}
+826
View File
File diff suppressed because it is too large Load Diff
+10 -3
View File
@@ -6,7 +6,9 @@ const user = table(
{
name: "user",
public: true,
indexes: [{ accessor: "by_online", algorithm: "btree", columns: ["online"] }],
indexes: [
{ accessor: "by_online", algorithm: "btree", columns: ["online"] },
],
},
{
identity: t.identity().primaryKey(),
@@ -467,7 +469,12 @@ export const upload_avatar = spacetimedb.reducer(
if (data.length > 1024 * 1024) {
throw new SenderError("Avatar exceeds 1MB limit");
}
const img = ctx.db.image.insert({ id: 0n, data, mime_type: mimeType, name: "avatar" });
const img = ctx.db.image.insert({
id: 0n,
data,
mime_type: mimeType,
name: "avatar",
});
const user = ctx.db.user.identity.find(ctx.sender);
if (!user) throw new SenderError("User not found");
ctx.db.user.identity.update({ ...user, avatar_id: img.id });
@@ -1049,7 +1056,7 @@ export const send_message = spacetimedb.reducer(
export const init = spacetimedb.init((ctx) => {
let hasServers = false;
for (const _ of ctx.db.server.iter()) {
for (const _server of ctx.db.server.iter()) {
hasServers = true;
break;
}
+2 -6
View File
@@ -2,10 +2,6 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
"windows": ["main"],
"permissions": ["core:default"]
}
+2 -10
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from "svelte";
import { auth } from "./auth.svelte";
import { HOST_KEY, DB_NAME_KEY, getEnv } from "../env";
import { HOST_KEY, DB_NAME_KEY } from "../env";
import { TokenStore, getStdbHost, getStdbDbName } from "../config";
import SpacetimeProvider from "../SpacetimeProvider.svelte";
@@ -9,7 +9,7 @@
children: any,
reconnect: () => void,
showServerSettings: boolean,
onToggleServerSettings: (val: boolean) => void,
onToggleServerSettings: (_val: boolean) => void,
reconnectKey: number
}>();
@@ -22,7 +22,6 @@
const MAINCLOUD_URI = "https://maincloud.spacetimedb.com";
let isMaincloud = $state(true);
let userWantsToConnect = $state(false);
let isSettingsExpanded = $state(false);
onMount(() => {
@@ -48,10 +47,6 @@
if (stdbDbName) localStorage.setItem(DB_NAME_KEY, stdbDbName);
});
function handleAuthError(error: string | null) {
authError = error;
}
const isBypassEnabled =
import.meta.env.VITE_BYPASS_AUTH === "true" ||
new URLSearchParams(window.location.search).has("bypass_auth");
@@ -77,7 +72,6 @@
<SpacetimeProvider
onReconnectTrigger={reconnect}
onCancel={() => {
userWantsToConnect = false;
isGuest = false;
}}
>
@@ -106,7 +100,6 @@
<div style="display: flex; flex-direction: column; gap: 12px; width: 100%">
<button
onclick={() => {
userWantsToConnect = true;
auth.signinRedirect();
}}
disabled={auth.isLoading}
@@ -119,7 +112,6 @@
<button
onclick={() => {
isGuest = true;
userWantsToConnect = true;
}}
class="btn-secondary"
style="width: 100%"
+1 -1
View File
@@ -105,7 +105,7 @@ class AuthStore {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
keysToRemove.forEach((key) => localStorage.removeItem(key));
if (this.#user) {
await this.#userManager.signoutRedirect();
-3
View File
@@ -4,9 +4,7 @@
import { portal } from "../../portal";
import type { ChatService } from "../services/chat.svelte";
import type { WebRTCService } from "../services/webrtc/webrtc.svelte";
import type { WebRTCStats } from "../services/webrtc/types";
import Avatar from "./Avatar.svelte";
import type * as Types from "../../module_bindings/types";
const chat = getContext<ChatService>("chat");
const webrtc = getContext<WebRTCService>("webrtc");
@@ -183,7 +181,6 @@
{@const isWatchingMe = chat.watching.some(w => w.watcher.isEqual(vs.identity) && w.watchee.isEqual(chat.identity))}
{@const amISharing = chat.currentVoiceState?.isSharingScreen}
{@const voiceStatusColor = isMe ? "green" : getStatusColor(status)}
{@const videoStatusColor = isMe ? (isSharing ? "green" : undefined) : (isSharing ? getStatusColor(status) : undefined)}
{@const isLocalUserInThisChannel = chat.connectedVoiceChannel?.id === channel.id}
<div
+6 -8
View File
@@ -1,13 +1,11 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { EMOJIS, type EmojiInfo } from "../emojis";
import { EMOJIS } from "../emojis";
import type { ChatService } from "../services/chat.svelte";
import type * as Types from "../../module_bindings/types";
import { optimizeEmoji } from "../utils";
let { onSelect, onClose } = $props<{
onSelect: (emoji?: string, customEmojiId?: bigint) => void;
onClose: () => void;
let { onSelect } = $props<{
onSelect: (_emoji?: string, _customEmojiId?: bigint) => void;
}>();
const chat = getContext<ChatService>("chat");
@@ -22,7 +20,7 @@
recentEmojis = JSON.parse(saved).map((id: any) =>
typeof id === 'string' && id.startsWith('custom:') ? BigInt(id.split(':')[1]) : id
);
} catch (e) {
} catch {
recentEmojis = [];
}
}
@@ -65,8 +63,8 @@
{ id: 'custom', name: 'Custom', icon: '✨' },
];
async function handleCustomUpload(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
async function handleCustomUpload(_e: Event) {
const file = (_e.target as HTMLInputElement).files?.[0];
if (!file) return;
// We'll assume the user provides a name or we use the filename
+2 -4
View File
@@ -1,5 +1,4 @@
<script lang="ts">
import { onMount } from "svelte";
import type * as Types from "../../module_bindings/types";
let { image, onClose }: { image: Types.Image, onClose: () => void } = $props();
@@ -20,7 +19,6 @@
let hasMoved = false;
const isZoomed = $derived(zoomLevel > baseScale + 0.001);
const currentActualScale = $derived(zoomLevel);
function handleKeydown(e: KeyboardEvent) {
if (e.key === "Escape") {
@@ -130,7 +128,7 @@
isDragging = false;
}
function handleOverlayClick(e: MouseEvent) {
function handleOverlayClick() {
const clickDuration = Date.now() - mousedownTime;
if (hasMoved || clickDuration > 300) return;
onClose();
@@ -424,6 +422,6 @@
}
.full-image.animate {
transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
transition: transform 0.05s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
+1 -1
View File
@@ -43,7 +43,7 @@
if (!title && !description && !image) {
throw new Error("No useful metadata found");
}
} catch (err) {
} catch {
if (!isCancelled) {
error = true;
}
+1 -1
View File
@@ -5,7 +5,7 @@
let {
activeChannelId,
activeThreadId,
activeThreadId: _activeThreadId,
isFullyAuthenticated
} = $props<{
activeChannelId: bigint | null,
+2 -2
View File
@@ -13,8 +13,8 @@
let {
threadMessages,
users,
identity,
images
identity: _identity,
images: _images
}: {
threadMessages: readonly Types.Message[],
users: readonly Types.User[],
+1 -3
View File
@@ -1,8 +1,6 @@
<script lang="ts">
import { Identity } from "spacetimedb";
import type * as Types from "../../module_bindings/types";
import { useReducer } from "spacetimedb/svelte";
import { reducers } from "../../module_bindings";
import ThreadMessageList from "./ThreadMessageList.svelte";
import ThreadMessageInput from "./ThreadMessageInput.svelte";
import RichText from "./RichText.svelte";
@@ -15,7 +13,7 @@
pendingThreadParentMessageId,
setPendingThreadParentMessageId,
activeChannelId,
activeServer,
activeServer: _activeServer,
isFullyAuthenticated,
users,
identity,
+1 -4
View File
@@ -1,8 +1,7 @@
<script lang="ts">
import { Identity } from "spacetimedb";
import { getContext, onMount, onDestroy } from "svelte";
import { onMount } from "svelte";
import Avatar from "./Avatar.svelte";
import type { WebRTCService } from "../services/webrtc/webrtc.svelte";
import type * as Types from "../../module_bindings/types";
let {
@@ -27,8 +26,6 @@
users: readonly Types.User[];
}>();
const webrtc = getContext<WebRTCService>("webrtc");
let videoRef = $state<HTMLVideoElement | null>(null);
let containerRef = $state<HTMLDivElement | null>(null);
let isMuted = $state(false);
+15 -3
View File
@@ -1024,7 +1024,11 @@ export const EMOJIS: EmojiInfo[] = [
{ char: "🈁", name: "Japanese “here” button", category: "symbols" },
{ char: "🈂️", name: "Japanese “service charge” button", category: "symbols" },
{ char: "🈷️", name: "Japanese “monthly amount” button", category: "symbols" },
{ char: "🈶", name: "Japanese “not free of charge” button", category: "symbols" },
{
char: "🈶",
name: "Japanese “not free of charge” button",
category: "symbols",
},
{ char: "🈯", name: "Japanese “reserved” button", category: "symbols" },
{ char: "🉐", name: "Japanese “bargain” button", category: "symbols" },
{ char: "🈹", name: "Japanese “discount” button", category: "symbols" },
@@ -1034,9 +1038,17 @@ export const EMOJIS: EmojiInfo[] = [
{ char: "🈸", name: "Japanese “application” button", category: "symbols" },
{ char: "🈴", name: "Japanese “passing grade” button", category: "symbols" },
{ char: "🈳", name: "Japanese “vacancy” button", category: "symbols" },
{ char: "㊗️", name: "Japanese “congratulations” button", category: "symbols" },
{
char: "㊗️",
name: "Japanese “congratulations” button",
category: "symbols",
},
{ char: "㊙️", name: "Japanese “secret” button", category: "symbols" },
{ char: "🈺", name: "Japanese “open for business” button", category: "symbols" },
{
char: "🈺",
name: "Japanese “open for business” button",
category: "symbols",
},
{ char: "🈵", name: "Japanese “no vacancy” button", category: "symbols" },
{ char: "🔴", name: "red circle", category: "symbols" },
{ char: "🟠", name: "orange circle", category: "symbols" },
+18 -5
View File
@@ -39,7 +39,7 @@ export class ChatService {
// Background effect to populate avatar cache
$effect(() => {
const usersWithAvatars = this.users.filter(u => u.avatarId);
const usersWithAvatars = this.users.filter((u) => u.avatarId);
for (const user of usersWithAvatars) {
const avatarId = user.avatarId!;
const idStr = avatarId.toString();
@@ -64,7 +64,7 @@ export class ChatService {
}
// 2. If not in cache, check if we have the image data in SpacetimeDB sync
const img = this.images.find(i => i.id === id);
const img = this.images.find((i) => i.id === id);
if (img) {
const url = await imageCache.set(id, img.data, img.mimeType);
this.#avatarUrls.set(idStr, url);
@@ -356,7 +356,11 @@ export class ChatService {
this.#msg.handleLoadMoreMessages();
};
handleToggleReaction = (messageId: bigint, emoji?: string, customEmojiId?: bigint) => {
handleToggleReaction = (
messageId: bigint,
emoji?: string,
customEmojiId?: bigint,
) => {
this.#msg.toggleReaction(messageId, emoji, customEmojiId);
};
@@ -377,7 +381,11 @@ export class ChatService {
this.#msg.uploadImage(data, mimeType, name);
};
uploadCustomEmoji = async (name: string, category: string, data: Uint8Array) => {
uploadCustomEmoji = async (
name: string,
category: string,
data: Uint8Array,
) => {
this.#msg.uploadCustomEmoji(name, category, data);
};
@@ -420,7 +428,12 @@ export class ChatService {
})
.map((ta) => {
const user = this.users.find((u) => u.identity.isEqual(ta.identity));
return user || { name: `User ${ta.identity.toHexString().substring(0, 8)}` } as any as Types.User;
return (
user ||
({
name: `User ${ta.identity.toHexString().substring(0, 8)}`,
} as any as Types.User)
);
});
}
+62 -23
View File
@@ -26,12 +26,18 @@ export class MessagingService {
messageLimit = $state(50);
isLoadingMore = $state(false);
constructor(db: DatabaseService, nav: NavigationService, identity: () => Identity | null) {
constructor(
db: DatabaseService,
nav: NavigationService,
identity: () => Identity | null,
) {
this.#db = db;
this.#nav = nav;
this.#identity = identity;
this.#createThreadWithMessageReducer = useReducer(reducers.createThreadWithMessage);
this.#createThreadWithMessageReducer = useReducer(
reducers.createThreadWithMessage,
);
this.#sendMessageReducer = useReducer(reducers.sendMessage);
this.#uploadImageReducer = useReducer(reducers.uploadImage);
this.#uploadCustomEmojiReducer = useReducer(reducers.uploadCustomEmoji);
@@ -42,9 +48,15 @@ export class MessagingService {
const [messageImagesStore] = useTable(tables.message_image);
const [messageReactionsStore] = useTable(tables.message_reaction);
messagesStore.subscribe((v: readonly Types.Message[]) => (this.#allMessages = v));
messageImagesStore.subscribe((v: readonly Types.MessageImage[]) => (this.#messageImages = v));
messageReactionsStore.subscribe((v: readonly Types.MessageReaction[]) => (this.#messageReactions = v));
messagesStore.subscribe(
(v: readonly Types.Message[]) => (this.#allMessages = v),
);
messageImagesStore.subscribe(
(v: readonly Types.MessageImage[]) => (this.#messageImages = v),
);
messageReactionsStore.subscribe(
(v: readonly Types.MessageReaction[]) => (this.#messageReactions = v),
);
$effect(() => {
const channelId = this.#nav.activeChannelId;
@@ -57,38 +69,53 @@ export class MessagingService {
if (!conn) return;
untrack(() => {
const queries = [
"SELECT * FROM server",
"SELECT * FROM custom_emoji",
];
const queries = ["SELECT * FROM server", "SELECT * FROM custom_emoji"];
// 1. Surgical Membership & Identity Pruning
// Only load our own memberships and those of the active server
if (identity && serverId) {
queries.push(`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()} OR server_id = ${serverId}`);
queries.push(
`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()} OR server_id = ${serverId}`,
);
} else if (identity) {
queries.push(`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()}`);
queries.push(
`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()}`,
);
}
if (serverId) {
// 2. Metadata for the Active Server
queries.push(`SELECT * FROM channel WHERE server_id = ${serverId}`);
queries.push(`SELECT * FROM thread WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
queries.push(
`SELECT * FROM thread WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
);
// Voice states and activity are lightweight and indexed by channel_id
queries.push(`SELECT * FROM voice_state WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
queries.push(`SELECT * FROM voice_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
queries.push(`SELECT * FROM typing_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
queries.push(`SELECT * FROM watching WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
queries.push(
`SELECT * FROM voice_state WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
);
queries.push(
`SELECT * FROM voice_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
);
queries.push(
`SELECT * FROM typing_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
);
queries.push(
`SELECT * FROM watching WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
);
// 3. Load the actual messages for the active channel (with pagination)
if (channelId) {
queries.push(`SELECT * FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit}`);
queries.push(
`SELECT * FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit}`,
);
}
// If viewing a thread, pull those specific messages too
if (threadId) {
queries.push(`SELECT * FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId})`);
queries.push(
`SELECT * FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId})`,
);
}
// 4. Surgical Image & Reaction Sync
@@ -98,8 +125,12 @@ export class MessagingService {
? `(SELECT id FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId}))`
: `(SELECT id FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit})`;
queries.push(`SELECT * FROM message_image WHERE message_id IN ${visibleMsgSubquery}`);
queries.push(`SELECT * FROM message_reaction WHERE message_id IN ${visibleMsgSubquery}`);
queries.push(
`SELECT * FROM message_image WHERE message_id IN ${visibleMsgSubquery}`,
);
queries.push(
`SELECT * FROM message_reaction WHERE message_id IN ${visibleMsgSubquery}`,
);
// Image Sync: Message Images + User Avatars
const userSubquery = `(SELECT identity FROM user WHERE online = true OR identity IN (SELECT identity FROM server_member WHERE server_id = ${serverId}) OR identity IN (SELECT sender FROM message WHERE id IN ${visibleMsgSubquery}))`;
@@ -130,7 +161,7 @@ export class MessagingService {
});
// Reset limit on channel switch
$effect(() => {
const _ = this.#nav.activeChannelId;
void this.#nav.activeChannelId;
untrack(() => {
this.messageLimit = 50;
});
@@ -216,11 +247,19 @@ export class MessagingService {
this.#uploadImageReducer({ data, mimeType, name });
};
uploadCustomEmoji = async (name: string, category: string, data: Uint8Array) => {
uploadCustomEmoji = async (
name: string,
category: string,
data: Uint8Array,
) => {
this.#uploadCustomEmojiReducer({ name, category, data });
};
toggleReaction = (messageId: bigint, emoji?: string, customEmojiId?: bigint) => {
toggleReaction = (
messageId: bigint,
emoji?: string,
customEmojiId?: bigint,
) => {
this.#toggleReactionReducer({ messageId, emoji, customEmojiId });
};
@@ -71,7 +71,7 @@ export class ChannelAudioWebRTCService {
$effect(() => {
const audioTrack = this.localStream?.getAudioTracks()[0] || null;
// Accessing peers and peerStatuses to trigger effect on changes
const _statuses = this.peerManager.peerStatuses;
void this.peerManager.peerStatuses;
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
const transceivers = peer.pc.getTransceivers();
if (transceivers[0] && transceivers[0].sender.track !== audioTrack) {
@@ -224,7 +224,10 @@ export class LocalMediaService {
// Persist screen share settings
$effect(() => {
localStorage.setItem("screen_share_resolution", this.screenShareResolution);
localStorage.setItem(
"screen_share_resolution",
this.screenShareResolution,
);
});
$effect(() => {
localStorage.setItem("screen_share_framerate", this.screenShareFramerate);
@@ -331,7 +334,8 @@ export class LocalMediaService {
try {
console.log("[local-media] Requesting screen share...");
const res = RESOLUTIONS[this.screenShareResolution] || RESOLUTIONS["1080"];
const res =
RESOLUTIONS[this.screenShareResolution] || RESOLUTIONS["1080"];
const { width, height } = res;
const frameRate = parseInt(this.screenShareFramerate);
@@ -363,10 +367,10 @@ export class LocalMediaService {
};
stopScreenShare = (
onTrackCleared: (track: MediaStreamTrack | null) => void,
onTrackCleared: (_track: MediaStreamTrack | null) => void,
) => {
if (this.localScreenStream) {
this.localScreenStream.getTracks().forEach((track) => track.stop());
this.localScreenStream.getTracks().forEach((t) => t.stop());
this.localScreenStream = null;
this.#setSharingScreen({ sharing: false });
onTrackCleared(null);
@@ -17,15 +17,15 @@ export class PeerManagerService {
mediaType = $state<"voice" | "screen">("voice");
isDeafened = $state(false);
targetBitrate = $state<number>(5000000);
onNegotiationNeeded: (peerIdHex: string, pc: RTCPeerConnection) => void;
onIceCandidate: (peerIdHex: string, candidate: RTCIceCandidate) => void;
onNegotiationNeeded: (_peerIdHex: string, _pc: RTCPeerConnection) => void;
onIceCandidate: (_peerIdHex: string, _candidate: RTCIceCandidate) => void;
constructor(
identity: Identity | null,
mediaType: "voice" | "screen",
isDeafened: boolean,
onNegotiationNeeded: (peerIdHex: string, pc: RTCPeerConnection) => void,
onIceCandidate: (peerIdHex: string, candidate: RTCIceCandidate) => void,
onNegotiationNeeded: (_peerIdHex: string, _pc: RTCPeerConnection) => void,
onIceCandidate: (_peerIdHex: string, _candidate: RTCIceCandidate) => void,
) {
this.identity = identity;
this.mediaType = mediaType;
@@ -68,7 +68,7 @@ export class ScreenSharingWebRTCService {
const videoTrack = this.localScreenStream?.getVideoTracks()[0] || null;
const audioTrack = this.localScreenStream?.getAudioTracks()[0] || null;
// Accessing peers and peerStatuses to trigger effect on changes
const _statuses = this.peerManager.peerStatuses;
void this.peerManager.peerStatuses;
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
const transceivers = peer.pc.getTransceivers();
let changed = false;
+6 -4
View File
@@ -1,5 +1,3 @@
import { Identity } from "spacetimedb";
export interface Peer {
pc: RTCPeerConnection;
audio?: HTMLAudioElement;
@@ -33,8 +31,12 @@ export interface LocalMediaState {
isSharingScreen: boolean;
toggleMute: () => void;
toggleDeafen: () => void;
startScreenShare: (peerManager: any) => Promise<void>;
stopScreenShare: (peerManager: any) => void;
startScreenShare: (
onTrackReady: (_track: MediaStreamTrack) => void,
) => Promise<void>;
stopScreenShare: (
onTrackCleared: (_track: MediaStreamTrack | null) => void,
) => void;
requestMic: () => Promise<void>;
releaseMic: () => void;
}
+2 -1
View File
@@ -91,7 +91,8 @@ export class WebRTCService {
this.screen.identity = this.identity;
this.screen.connectedChannelId = this.connectedChannelId;
this.screen.localScreenStream = this.localMedia.localScreenStream;
this.screen.peerManager.targetBitrate = parseFloat(this.localMedia.screenShareBitrate) * 1000000;
this.screen.peerManager.targetBitrate =
parseFloat(this.localMedia.screenShareBitrate) * 1000000;
});
// Sync mute/deafen to DB
+2 -2
View File
@@ -113,8 +113,8 @@ export const optimizeEmoji = async (
// Draw image centered and cropped to square
const scale = Math.max(DIM / img.width, DIM / img.height);
const x = (DIM / 2) - (img.width / 2) * scale;
const y = (DIM / 2) - (img.height / 2) * scale;
const x = DIM / 2 - (img.width / 2) * scale;
const y = DIM / 2 - (img.height / 2) * scale;
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
canvas.toBlob(
+13 -6
View File
@@ -7,8 +7,8 @@ export { HOST_KEY, DB_NAME_KEY, getEnv };
const normalizeHost = (host: string) => {
try {
const url = new URL(host.includes("://") ? host : `https://${host}`);
return url.origin.replace(/^http/, 'ws'); // Ensure ws/wss
} catch (e) {
return url.origin.replace(/^http/, "ws"); // Ensure ws/wss
} catch {
return host.trim().replace(/\/+$/, ""); // Fallback
}
};
@@ -25,11 +25,15 @@ export const TokenStore = {
clear: (host: string, dbName: string) => {
const key = `${normalizeHost(host)}/${dbName}/auth_token`;
localStorage.removeItem(key);
}
},
};
export const getStdbHost = () => localStorage.getItem(HOST_KEY) || getEnv("VITE_SPACETIMEDB_HOST", "https://maincloud.spacetimedb.com");
export const getStdbDbName = () => localStorage.getItem(DB_NAME_KEY) || getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord");
export const getStdbHost = () =>
localStorage.getItem(HOST_KEY) ||
getEnv("VITE_SPACETIMEDB_HOST", "https://maincloud.spacetimedb.com");
export const getStdbDbName = () =>
localStorage.getItem(DB_NAME_KEY) ||
getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord");
let _connection: DbConnection | null = null;
export const getConnection = () => _connection;
@@ -89,7 +93,10 @@ class ConnectionManager {
};
}
export const connectionBuilder = (onReconnectTrigger: () => void, oidcToken?: string) => {
export const connectionBuilder = (
onReconnectTrigger: () => void,
oidcToken?: string,
) => {
const host = getStdbHost();
const dbName = getStdbDbName();
+7 -4
View File
@@ -1,12 +1,15 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import basicSsl from '@vitejs/plugin-basic-ssl';
import basicSsl from "@vitejs/plugin-basic-ssl";
import { cloudflare } from "@cloudflare/vite-plugin";
// https://vite.dev/config/
export default defineConfig({
plugins: [
process.env.VITE_USE_SSL === 'true' ? basicSsl() : [],
process.env.VITE_USE_SSL === "true" ? basicSsl() : [],
svelte(),
cloudflare()
],
// Prevent vite from obscuring rust errors
clearScreen: false,
@@ -14,7 +17,7 @@ export default defineConfig({
// Tauri expects a fixed port, fail if that port is not available
server: {
strictPort: true,
port: process.env.VITE_USE_SSL === 'true' ? 5174 : 5173,
port: process.env.VITE_USE_SSL === "true" ? 5174 : 5173,
host: "0.0.0.0",
},
});
});