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';