diff --git a/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx b/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx index 9bda2db944..76b17f545d 100644 --- a/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx +++ b/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx @@ -19,9 +19,11 @@ import { Tabs, Tab, makeStyles } from '@material-ui/core'; import { Page, pageTheme, Content, Header, HeaderLabel } from '@backstage/core'; import 'graphiql/graphiql.css'; import GraphiQL from 'graphiql'; +import { StorageBucket } from 'lib/storage'; const tabs = [ { + id: 'gitlab', title: 'GitLab', fetcher: async (params: any) => { const res = await fetch('https://gitlab.com/api/graphql', { @@ -33,6 +35,7 @@ const tabs = [ }, }, { + id: 'countries', title: 'Countries', fetcher: async (params: any) => { const res = await fetch('https://countries.trevorblades.com/', { @@ -59,6 +62,9 @@ export const GraphiQLPage: FC<{}> = () => { const classes = useStyles(); const [tabIndex, setTabIndex] = useState(0); + const { id, fetcher } = tabs[tabIndex]; + const storage = StorageBucket.forLocalStorage(`plugin/graphiql/data/${id}`); + return (
@@ -71,7 +77,7 @@ export const GraphiQLPage: FC<{}> = () => { ))} - + ); diff --git a/plugins/graphiql/src/lib/storage/StorageBucket.test.ts b/plugins/graphiql/src/lib/storage/StorageBucket.test.ts new file mode 100644 index 0000000000..2b6046aefe --- /dev/null +++ b/plugins/graphiql/src/lib/storage/StorageBucket.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StorageBucket } from './StorageBucket'; + +describe('StorageBucket', () => { + it('should forbid access to unknown keys', () => { + const bucket = StorageBucket.forStorage(localStorage, 'hello'); + + expect(() => { + bucket['dunno-this-one'] = 'nope'; + }).toThrow('Direct property access is not allowed for StorageBuckets'); + expect(() => { + return bucket['dunno-this-one']; + }).toThrow('Direct property access is not allowed for StorageBuckets'); + }); +}); diff --git a/plugins/graphiql/src/lib/storage/StorageBucket.ts b/plugins/graphiql/src/lib/storage/StorageBucket.ts new file mode 100644 index 0000000000..eb8feb394d --- /dev/null +++ b/plugins/graphiql/src/lib/storage/StorageBucket.ts @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type BucketData = { [key in string]: string }; + +export class StorageBucket implements Storage { + private static noPropAccessProxyHandler: ProxyHandler = { + get(target, prop) { + if (prop in target) { + return target[prop as any]; + } + throw new Error( + 'Direct property access is not allowed for StorageBuckets', + ); + }, + set() { + throw new Error( + 'Direct property access is not allowed for StorageBuckets', + ); + }, + }; + + static forStorage(storage: Storage, bucket: string) { + const storageBucket = new StorageBucket(storage, bucket); + return new Proxy(storageBucket, StorageBucket.noPropAccessProxyHandler); + } + + static forLocalStorage(bucket: string): StorageBucket { + return StorageBucket.forStorage(localStorage, bucket); + } + + private constructor( + private readonly storage: Storage, + private readonly bucket: string, + ) {} + + [name: string]: any; + + get length(): number { + throw new Error('Method not implemented.'); + } + + clear(): void { + this.write({}); + } + + getItem(key: string): string | null { + return this.read()?.[key] ?? null; + } + + key(): never { + throw new Error('Method not implemented.'); + } + + removeItem(key: string): void { + const data = this.read(); + if (!data) { + return; + } + + const deleted = delete data[key]; + if (deleted) { + this.write(data); + } + } + + setItem(key: string, value: string): void { + const data = this.read() ?? {}; + data[key] = value; + this.write(data); + } + + private read(): BucketData | undefined { + const bucketValue = this.storage.getItem(this.bucket); + if (!bucketValue) { + return undefined; + } + + try { + return JSON.parse(bucketValue); + } catch { + return undefined; + } + } + + private write(data: BucketData) { + this.storage.setItem(this.bucket, JSON.stringify(data)); + } +} diff --git a/plugins/graphiql/src/lib/storage/index.ts b/plugins/graphiql/src/lib/storage/index.ts new file mode 100644 index 0000000000..a760761e44 --- /dev/null +++ b/plugins/graphiql/src/lib/storage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './StorageBucket';