2 Commits
Author SHA1 Message Date
Sirttas 5a974bac8f refactor use axios for fetch 2026-07-07 21:56:44 +02:00
Sirttas 98380a06b9 feat: reload on ledger acquisitions 2026-07-07 18:57:42 +02:00
4 changed files with 20 additions and 43 deletions
+6 -21
View File
@@ -1,8 +1,6 @@
import log from "loglevel"; import log from "loglevel";
import {getAccessToken} from "@/auth/token"; import {activityApi} from "@/mammon";
import {mammonUrl, refreshAccessToken} from "@/mammon";
const STREAM_URL = mammonUrl + "activities/processed";
const RECONNECT_DELAY_MILLIS = 3_000; const RECONNECT_DELAY_MILLIS = 3_000;
const FRAME_SEPARATOR = "\n\n"; const FRAME_SEPARATOR = "\n\n";
const PROCESSED_EVENT = "event:processed"; const PROCESSED_EVENT = "event:processed";
@@ -48,33 +46,20 @@ const consume = async (body: ReadableStream<Uint8Array>) => {
} }
}; };
const connect = async (retried = false): Promise<void> => { const connect = async (): Promise<void> => {
if (listeners.size === 0) { if (listeners.size === 0) {
return; return;
} }
const signal = controller?.signal; const signal = controller?.signal;
const token = getAccessToken();
try { try {
const response = await fetch(STREAM_URL, { const response = await activityApi.streamProcessing({
headers: { responseType: "stream",
Accept: "text/event-stream", adapter: "fetch",
...(token ? {Authorization: `Bearer ${token}`} : {}),
},
signal, signal,
}); });
if (response.status === 401) { await consume(response.data as unknown as ReadableStream<Uint8Array>);
if (!retried && await refreshAccessToken() && listeners.size > 0) {
return connect(true);
}
return;
}
if (!response.ok || !response.body) {
throw new Error(`Activity processing SSE stream failed with ${response.status}`);
}
await consume(response.body);
scheduleReconnect(); scheduleReconnect();
} catch (error) { } catch (error) {
if (listeners.size === 0 || signal?.aborted) { if (listeners.size === 0 || signal?.aborted) {
+11 -1
View File
@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import {getMarketTypes, MarketTypePrice, useAppraisalStore} from "@/market"; import {getMarketTypes, MarketTypePrice, useAppraisalStore} from "@/market";
import {ref, watch} from 'vue'; import {ref, watch} from 'vue';
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
import {useAutoRefresh} from '@/composables';
import {AcquiredType} from './AcquiredType'; import {AcquiredType} from './AcquiredType';
import {RawAcquiredType} from './acquisition'; import {RawAcquiredType} from './acquisition';
import AcquisitionResultTable from './AcquisitionResultTable.vue'; import AcquisitionResultTable from './AcquisitionResultTable.vue';
@@ -44,10 +46,18 @@ const refresh = async (itms: RawAcquiredType[] = props.items) => {
watch(() => props.items, refresh, {immediate: true}); watch(() => props.items, refresh, {immediate: true});
defineExpose({refresh}); const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(() => refresh(), 5 * 60 * 1000);
</script> </script>
<template> <template>
<div class="flex">
<div class="ms-auto flex">
<button class="rounded-e-none" @click="refresh()">Refresh</button>
<button class="rounded-s-none border-s-0 flex items-center" :class="{ 'bg-slate-700': autoRefresh }" title="Auto refresh" @click="toggleAutoRefresh">
<ArrowPathIcon class="w-5 h-5" :class="{ 'animate-spin': autoRefresh }" />
</button>
</div>
</div>
<template v-if="enriched.length > 0"> <template v-if="enriched.length > 0">
<AcquisitionResultTable :items="enriched" :ignoredColumns="ignoredColumns" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell }, props.ledgerId)" @sell="types => sellModal?.open(types, props.ledgerId)" /> <AcquisitionResultTable :items="enriched" :ignoredColumns="ignoredColumns" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell }, props.ledgerId)" @sell="types => sellModal?.open(types, props.ledgerId)" />
<BuyModal ref="buyModal" /> <BuyModal ref="buyModal" />
+1 -19
View File
@@ -1,29 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import {AcquisitionsPanel, useAcquiredTypesStore} from '@/market/acquisition'; import {AcquisitionsPanel, useAcquiredTypesStore} from '@/market/acquisition';
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
import {useAutoRefresh} from '@/composables';
import {ref} from 'vue';
const acquiredTypesStore = useAcquiredTypesStore(); const acquiredTypesStore = useAcquiredTypesStore();
const panel = ref<InstanceType<typeof AcquisitionsPanel>>();
const refresh = async () => {
await panel.value?.refresh();
}
const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(refresh, 5 * 60 * 1000);
</script> </script>
<template> <template>
<div class="mt-4"> <div class="mt-4">
<div class="flex"> <AcquisitionsPanel :items="acquiredTypesStore.acquiredTypes" ignoredColumns="date" />
<div class="ms-auto flex">
<button class="rounded-e-none" @click="refresh">Refresh</button>
<button class="rounded-s-none border-s-0 flex items-center" :class="{ 'bg-slate-700': autoRefresh }" title="Auto refresh" @click="toggleAutoRefresh">
<ArrowPathIcon class="w-5 h-5" :class="{ 'animate-spin': autoRefresh }" />
</button>
</div>
</div>
<AcquisitionsPanel ref="panel" :items="acquiredTypesStore.acquiredTypes" ignoredColumns="date" />
</div> </div>
</template> </template>