Skip to content

Commit 6b6fe3b

Browse files
authored
Merge pull request #72 from cmu-delphi/rzatserkovnyi/persistent-apikeys
Add persistence to API keys
2 parents f6c12ab + 20fd21f commit 6b6fe3b

File tree

9 files changed

+98
-32
lines changed

9 files changed

+98
-32
lines changed

src/components/dialogs/ImportAPIDialog.svelte

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import NowCast from './dataSources/Nowcast.svelte';
2020
import CovidHosp from './dataSources/COVIDHosp.svelte';
2121
import CoviDcast from './dataSources/COVIDcast.svelte';
22-
import { navMode } from '../../store';
22+
import { navMode, storeApiKeys } from '../../store';
2323
import { NavMode } from '../chartUtils';
2424
2525
const dispatch = createEventDispatcher();
@@ -173,10 +173,26 @@
173173
{/if}
174174
</form>
175175

176-
<button slot="footer" class="uk-button uk-button-primary" type="submit" form={id} disabled={loading}>
177-
Fetch Data
178-
{#if loading}
179-
<div uk-spinner />
180-
{/if}
181-
</button>
176+
<div slot="footer">
177+
<div class="uk-form-controls uk-form-controls-text container">
178+
<button class="uk-button uk-button-primary" type="submit" form={id} disabled={loading}>
179+
Fetch Data
180+
{#if loading}
181+
<div uk-spinner />
182+
{/if}
183+
</button>
184+
<label
185+
><input class="uk-checkbox" type="checkbox" bind:checked={$storeApiKeys} />
186+
Save API key (auth token) between visits</label
187+
>
188+
</div>
189+
</div>
182190
</Dialog>
191+
192+
<style>
193+
.container {
194+
display: flex;
195+
align-items: center;
196+
column-gap: 2em;
197+
}
198+
</style>

src/components/dialogs/dataSources/CDC.svelte

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@
33
import { cdcLocations as regions } from '../../../data/data';
44
import SelectField from '../inputs/SelectField.svelte';
55
import TextField from '../inputs/TextField.svelte';
6+
import { apiKey } from '../../../store';
67
78
export let id: string;
89
910
let locations = regions[0].value;
10-
let auth = '';
1111
1212
export function importDataSet() {
13-
return importCDC({ locations, auth });
13+
return importCDC({ locations, auth: $apiKey });
1414
}
1515
</script>
1616

17-
<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
17+
<TextField
18+
id="{id}-auth"
19+
name="auth"
20+
label="Authorizaton Token"
21+
bind:value={$apiKey}
22+
placeholder="authorization token"
23+
/>
1824
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />

src/components/dialogs/dataSources/COVIDcast.svelte

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
import type { LabelValue } from '../../../data/data';
55
import SelectField from '../inputs/SelectField.svelte';
66
import TextField from '../inputs/TextField.svelte';
7+
import { apiKey } from '../../../store';
78
89
export let id: string;
910
10-
let api_key = '';
1111
let data_source = '';
1212
let signal = '';
1313
let geo_type = '';
1414
let geo_value = '';
15-
let form_key = '';
1615
let valid_key = true;
1716
1817
let dataSources: (LabelValue & { signals: string[] })[] = [];
@@ -36,12 +35,11 @@
3635
};
3736
3837
function fetchMetadata() {
39-
fetchCOVIDcastMeta(form_key).then((res) => {
38+
fetchCOVIDcastMeta($apiKey).then((res) => {
4039
if (res.length == 0) {
4140
valid_key = false;
4241
} else {
4342
valid_key = true;
44-
api_key = form_key; // API key is valid -> use it to fetch data later on
4543
geoTypes = [...new Set(res.map((d) => d.geo_type))];
4644
const byDataSource = new Map<string, LabelValue & { signals: string[] }>();
4745
for (const row of res) {
@@ -69,10 +67,10 @@
6967
});
7068
7169
export function importDataSet() {
72-
return fetchCOVIDcastMeta(api_key).then((res) => {
70+
return fetchCOVIDcastMeta($apiKey).then((res) => {
7371
const meta = res.filter((row) => row.data_source === data_source && row.signal === signal);
7472
const time_type = meta[0].time_type;
75-
return importCOVIDcast({ data_source, signal, geo_type, geo_value, time_type, api_key });
73+
return importCOVIDcast({ data_source, signal, geo_type, geo_value, time_type, api_key: $apiKey });
7674
});
7775
}
7876
</script>
@@ -89,7 +87,7 @@
8987
class:uk-form-danger={!valid_key}
9088
name="api_key"
9189
required={false}
92-
bind:value={form_key}
90+
bind:value={$apiKey}
9391
on:input={debounce(() => fetchMetadata(), 500)}
9492
/>
9593
{#if !valid_key}

src/components/dialogs/dataSources/FluView.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { importFluView } from '../../../api/EpiData';
33
import { fluViewRegions } from '../../../data/data';
4+
import { apiKey } from '../../../store';
45
import SelectField from '../inputs/SelectField.svelte';
56
import SelectIssue from '../inputs/SelectIssue.svelte';
67
import TextField from '../inputs/TextField.svelte';
@@ -10,10 +11,9 @@
1011
1112
let regions = fluViewRegions[0].value;
1213
let issue = DEFAULT_ISSUE;
13-
let auth: string = '';
1414
1515
export function importDataSet() {
16-
return importFluView({ regions, ...issue, auth });
16+
return importFluView({ regions, ...issue, auth: $apiKey });
1717
}
1818
</script>
1919

@@ -23,7 +23,7 @@
2323
id="{id}-auth"
2424
name="auth"
2525
label="Auth Key"
26-
bind:value={auth}
26+
bind:value={$apiKey}
2727
required={false}
2828
placeholder="authorization token"
2929
/>
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
<script lang="ts">
22
import { importGHT } from '../../../api/EpiData';
33
import { ghtLocations as regions } from '../../../data/data';
4+
import { apiKey } from '../../../store';
45
import SelectField from '../inputs/SelectField.svelte';
56
import TextField from '../inputs/TextField.svelte';
67
78
export let id: string;
89
910
let locations = regions[0].value;
10-
let auth = '';
1111
let query = '';
1212
1313
export function importDataSet() {
14-
return importGHT({ auth, locations, query });
14+
return importGHT({ auth: $apiKey, locations, query });
1515
}
1616
</script>
1717

18-
<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
18+
<TextField
19+
id="{id}-auth"
20+
name="auth"
21+
label="Authorizaton Token"
22+
bind:value={$apiKey}
23+
placeholder="authorization token"
24+
/>
1925
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
2026
<TextField id="{id}-query" name="query" label="Search Query or Topic" bind:value={query} />
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
<script lang="ts">
22
import { importQuidel } from '../../../api/EpiData';
33
import { quidelLocations as regions } from '../../../data/data';
4+
import { apiKey } from '../../../store';
45
import SelectField from '../inputs/SelectField.svelte';
56
import TextField from '../inputs/TextField.svelte';
67
78
export let id: string;
89
910
let locations = regions[0].value;
10-
let auth = '';
1111
1212
export function importDataSet() {
13-
return importQuidel({ auth, locations });
13+
return importQuidel({ auth: $apiKey, locations });
1414
}
1515
</script>
1616

17-
<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
17+
<TextField
18+
id="{id}-auth"
19+
name="auth"
20+
label="Authorizaton Token"
21+
bind:value={$apiKey}
22+
placeholder="authorization token"
23+
/>
1824
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
<script lang="ts">
22
import { importSensors } from '../../../api/EpiData';
33
import { sensorLocations as regions, sensorNames } from '../../../data/data';
4+
import { apiKey } from '../../../store';
45
import SelectField from '../inputs/SelectField.svelte';
56
import TextField from '../inputs/TextField.svelte';
67
78
export let id: string;
89
910
let locations = regions[0].value;
10-
let auth = '';
1111
let names = sensorNames[0].value;
1212
1313
export function importDataSet() {
14-
return importSensors({ auth, names, locations });
14+
return importSensors({ auth: $apiKey, names, locations });
1515
}
1616
</script>
1717

18-
<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
18+
<TextField
19+
id="{id}-auth"
20+
name="auth"
21+
label="Authorizaton Token"
22+
bind:value={$apiKey}
23+
placeholder="authorization token"
24+
/>
1925
<SelectField id="{id}-s" label="Name" bind:value={names} options={sensorNames} name="sensor" />
2026
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />

src/components/dialogs/dataSources/Twitter.svelte

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
<script lang="ts">
22
import { importTwitter } from '../../../api/EpiData';
33
import { twitterLocations as regions } from '../../../data/data';
4+
import { apiKey } from '../../../store';
45
import SelectField from '../inputs/SelectField.svelte';
56
import TextField from '../inputs/TextField.svelte';
67
78
export let id: string;
89
910
let locations = regions[0].value;
10-
let auth = '';
1111
let resolution: 'daily' | 'weekly' = 'daily';
1212
1313
export function importDataSet() {
14-
return importTwitter({ auth, locations, resolution });
14+
return importTwitter({ auth: $apiKey, locations, resolution });
1515
}
1616
</script>
1717

18-
<TextField id="{id}-auth" name="auth" label="Authorizaton Token" bind:value={auth} placeholder="authorization token" />
18+
<TextField
19+
id="{id}-auth"
20+
name="auth"
21+
label="Authorizaton Token"
22+
bind:value={$apiKey}
23+
placeholder="authorization token"
24+
/>
1925
<SelectField id="{id}-r" label="Location" bind:value={locations} options={regions} />
2026
<div>
2127
<div class="uk-form-label">Temporal Resolution</div>

src/store.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@ export const isShowingPoints = writable(defaults.showPoints);
1717
export const initialViewport = writable(defaults.viewport);
1818
export const navMode = writable(NavMode.autofit);
1919

20+
export const storeApiKeys = writable(localStorage.getItem('store-api-key') === 'true');
21+
storeApiKeys.subscribe((val) => {
22+
localStorage.setItem('store-api-key', val.toString());
23+
if (val) {
24+
// persist key from session to local storage
25+
localStorage.setItem('api-key', sessionStorage.getItem('api-key') || '');
26+
} else {
27+
// remove key from local storage
28+
localStorage.removeItem('api-key');
29+
}
30+
});
31+
32+
export const apiKey = writable(localStorage.getItem('api-key')! || '');
33+
apiKey.subscribe((val) => {
34+
// always keep key around in session storage (resets on page refresh)
35+
sessionStorage.setItem('api-key', val);
36+
if (localStorage.getItem('store-api-key') === 'true') {
37+
// store it in local storage (persistent)
38+
localStorage.setItem('api-key', val);
39+
}
40+
});
41+
2042
export function addDataSet(dataset: DataSet | DataGroup): void {
2143
const root = get(datasetTree);
2244
root.datasets.push(dataset);

0 commit comments

Comments
 (0)