Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions client-v3/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@
<em>{{ userStore.currentUser.username }}</em>
</template>
<BDropdownItem to="/me"> Settings </BDropdownItem>
<BDropdownItemButton @click.stop.prevent="userStore.logout()">
Sign Out
</BDropdownItemButton>
<BDropdownItemButton @click.stop.prevent="handleLogout"> Sign Out </BDropdownItemButton>
</BNavItemDropdown>
<BNavText id="connection-status" :class="{ healthy: websocketHealthy }">
<template v-if="websocketHealthy"> Connected </template>
Expand Down Expand Up @@ -401,6 +399,10 @@ async function switchServer(): Promise<void> {
window.location.reload();
}

async function handleLogout(): Promise<void> {
await userStore.logout();
}

async function goToLivePage(): Promise<void> {
const valid = await v$.value.$validate();
if (!valid) return;
Expand Down
10 changes: 2 additions & 8 deletions client-v3/src/components/show/config/script/ScriptEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,8 @@
<BRow class="script-row pt-1">
<BCol cols="10" class="ms-auto">
<BButtonGroup v-show="canEdit && !scriptConfigStore.cutMode" style="float: right">
<BDropdown
split
text="Add Dialogue"
variant="primary"
end
boundary="window"
@click="addNewLine(LINE_TYPES.DIALOGUE)"
>
<BButton variant="primary" @click="addNewLine(LINE_TYPES.DIALOGUE)">Add Dialogue</BButton>
<BDropdown variant="primary" end toggle-class="dropdown-toggle-split">
<BDropdownItem @click="addNewLine(LINE_TYPES.STAGE_DIRECTION)"
>Add Stage Direction</BDropdownItem
>
Expand Down
38 changes: 19 additions & 19 deletions client-v3/src/components/show/config/script/ScriptLineViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,26 @@
End
</BButton>
</BButtonGroup>
<BDropdown
v-else-if="canEdit && !isCutMode"
split
text="Edit"
end
boundary="window"
style="padding: 0"
variant="link"
@click.prevent.stop="$emit('editLine')"
>
<BDropdownItem @click.prevent.stop="$emit('insertDialogue')">Insert Dialogue</BDropdownItem>
<BDropdownItem @click.prevent.stop="$emit('insertStageDirection')"
>Insert Stage Direction</BDropdownItem
<BButtonGroup v-else-if="canEdit && !isCutMode">
<BButton variant="link" style="padding: 0" @click.prevent.stop="$emit('editLine')"
>Edit</BButton
>
<BDropdownItem @click.prevent.stop="$emit('insertCueLine')">Insert Cue Line</BDropdownItem>
<BDropdownItem @click.prevent.stop="$emit('insertSpacing')">Insert Spacing</BDropdownItem>
<BDropdownItem variant="danger" @click.prevent.stop="$emit('deleteLine')"
>Delete</BDropdownItem
>
</BDropdown>
<BDropdown end toggle-class="dropdown-toggle-split" variant="link" style="padding: 0">
<BDropdownItem @click.prevent.stop="$emit('insertDialogue')"
>Insert Dialogue</BDropdownItem
>
<BDropdownItem @click.prevent.stop="$emit('insertStageDirection')"
>Insert Stage Direction</BDropdownItem
>
<BDropdownItem @click.prevent.stop="$emit('insertCueLine')"
>Insert Cue Line</BDropdownItem
>
<BDropdownItem @click.prevent.stop="$emit('insertSpacing')">Insert Spacing</BDropdownItem>
<BDropdownItem variant="danger" @click.prevent.stop="$emit('deleteLine')"
>Delete</BDropdownItem
>
</BDropdown>
</BButtonGroup>
</BCol>
</BRow>
</template>
Expand Down
30 changes: 17 additions & 13 deletions client-v3/src/composables/useWebSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,24 @@ function connect(): void {
ws = new WebSocket(wsURL);

ws.onopen = async () => {
const wasErrored = errorCount > 0;
wsStore.$patch({ isConnected: true });
if (wasErrored) {
toast.success(
`WebSocket reconnected after ${errorCount} attempt${errorCount > 1 ? 's' : ''}`
);
}
log.info('WebSocket connected');
if (wasErrored) {
const { useShowStore } = await import('@/stores/show');
const showStore = useShowStore();
if (showStore.currentSession != null) {
await showStore.getShowSessionData();
try {
const wasErrored = errorCount > 0;
wsStore.$patch({ isConnected: true });
if (wasErrored) {
toast.success(
`WebSocket reconnected after ${errorCount} attempt${errorCount > 1 ? 's' : ''}`
);
}
log.info('WebSocket connected');
if (wasErrored) {
const { useShowStore } = await import('@/stores/show');
const showStore = useShowStore();
if (showStore.currentSession != null) {
await showStore.getShowSessionData();
}
}
} catch (e) {
log.error('Error in WebSocket onopen handler:', e);
}
};

Expand Down
37 changes: 34 additions & 3 deletions client-v3/src/js/http-interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,29 @@ function buildAuthenticatedOptions(
return { ...options, headers };
}

type QueueEntry = {
resolve: (r: Response) => void;
resource: string;
options: RequestInit & { headers: Record<string, string> };
};

export default function setupHttpInterceptor(): void {
const originalFetch = window.fetch;
const refreshState = { isRefreshing: false };
const refreshState = { isRefreshing: false, queue: [] as QueueEntry[] };

function flushQueue(newToken: string | null): void {
const entries = refreshState.queue.splice(0);
entries.forEach(({ resolve, resource, options }) => {
if (newToken) {
originalFetch(resource, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${newToken}` },
}).then(resolve);
} else {
resolve(new Response(JSON.stringify({ message: 'Session expired' }), { status: 401 }));
}
});
}

async function handle401Response(
resource: string,
Expand All @@ -30,16 +50,24 @@ export default function setupHttpInterceptor(): void {
isRefreshRequest: boolean,
response: Response
): Promise<Response> {
if (isRefreshRequest || refreshState.isRefreshing) {
log.warn('Token refresh failed with 401 or already refreshing, logging out');
if (isRefreshRequest) {
log.warn('Token refresh request received 401, logging out');
flushQueue(null);
toast.warning('Your session has expired. Please log in again.');
await userStore.logout();
return response;
}

if (refreshState.isRefreshing) {
return new Promise<Response>((resolve) => {
refreshState.queue.push({ resolve, resource, options: newOptions });
});
}

log.info('Attempting token refresh');
if (!userStore.authToken) {
log.warn('401 received with no token present');
flushQueue(null);
await userStore.logout();
return response;
}
Expand All @@ -51,18 +79,21 @@ export default function setupHttpInterceptor(): void {

if (!refreshSuccess) {
log.warn('Token refresh failed, logging out');
flushQueue(null);
toast.warning('Your session has expired. Please log in again.');
await userStore.logout();
return response;
}

log.info('Token refresh successful, retrying original request');
flushQueue(userStore.authToken);
return await originalFetch(resource, {
...newOptions,
headers: { ...newOptions.headers, Authorization: `Bearer ${userStore.authToken}` },
});
} catch (refreshError) {
refreshState.isRefreshing = false;
flushQueue(null);
log.error('Error during token refresh:', refreshError);
toast.error('Authentication error - please log in again');
await userStore.logout();
Expand Down
27 changes: 24 additions & 3 deletions client-v3/src/stores/show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,14 @@ export const useShowStore = defineStore('show', {
await this.getScriptRevisions();
toast.success('Added new script revision!');
} else {
toast.error('Unable to add new script revision');
let message = 'Unable to add new script revision';
try {
const data = await response.json();
if (data.message) message = data.message;
} catch {
/* non-JSON body */
}
toast.error(message);
}
},

Expand All @@ -767,7 +774,14 @@ export const useShowStore = defineStore('show', {
await this.getScriptRevisions();
toast.success('Deleted script revision!');
} else {
toast.error('Unable to delete script revision');
let message = 'Unable to delete script revision';
try {
const data = await response.json();
if (data.message) message = data.message;
} catch {
/* non-JSON body */
}
toast.error(message);
}
},

Expand All @@ -781,7 +795,14 @@ export const useShowStore = defineStore('show', {
await this.getScriptRevisions();
toast.success('Loaded script revision!');
} else {
toast.error('Unable to load script revision');
let message = 'Unable to load script revision';
try {
const data = await response.json();
if (data.message) message = data.message;
} catch {
/* non-JSON body */
}
toast.error(message);
}
},

Expand Down
Loading