fix: Unable to access localStorage in document embedded in iframe with third party cookies blocked (#4777)

* fix: Pasting from Microsoft Office pastes image. Closes #3058

* fix: Use Storage wrapper implementation for all editor calls to localStorage

closes #4776
This commit is contained in:
Tom Moor
2023-01-26 04:48:56 -08:00
committed by GitHub
parent 9ea606a734
commit cc14c212b6
9 changed files with 109 additions and 67 deletions

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { Primitive } from "utility-types";
import Storage from "@shared/utils/Storage";
import Logger from "~/utils/Logger";
import Storage from "~/utils/Storage";
import useEventListener from "./useEventListener";
type Options = {

View File

@@ -2,13 +2,13 @@ import { addDays, differenceInDays } from "date-fns";
import { floor } from "lodash";
import { action, autorun, computed, observable, set } from "mobx";
import { ExportContentType } from "@shared/types";
import Storage from "@shared/utils/Storage";
import parseTitle from "@shared/utils/parseTitle";
import { isRTL } from "@shared/utils/rtl";
import DocumentsStore from "~/stores/DocumentsStore";
import User from "~/models/User";
import type { NavigationNode } from "~/types";
import { client } from "~/utils/ApiClient";
import Storage from "~/utils/Storage";
import ParanoidModel from "./ParanoidModel";
import View from "./View";
import Field from "./decorators/Field";

View File

@@ -3,6 +3,7 @@ import invariant from "invariant";
import { observable, action, computed, autorun, runInAction } from "mobx";
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
import { TeamPreferences, UserPreferences } from "@shared/types";
import Storage from "@shared/utils/Storage";
import { getCookieDomain, parseDomain } from "@shared/utils/domains";
import RootStore from "~/stores/RootStore";
import Policy from "~/models/Policy";
@@ -11,7 +12,6 @@ import User from "~/models/User";
import env from "~/env";
import { client } from "~/utils/ApiClient";
import Desktop from "~/utils/Desktop";
import Storage from "~/utils/Storage";
const AUTH_STORE = "AUTH_STORE";
const NO_REDIRECT_PATHS = ["/", "/create", "/home"];

View File

@@ -1,8 +1,8 @@
import { action, autorun, computed, observable } from "mobx";
import { light as defaultTheme } from "@shared/styles/theme";
import Storage from "@shared/utils/Storage";
import Document from "~/models/Document";
import type { ConnectionStatus } from "~/scenes/Document/components/MultiplayerEditor";
import Storage from "~/utils/Storage";
const UI_STORE = "UI_STORE";

View File

@@ -1,56 +0,0 @@
/**
* Storage is a wrapper class for localStorage that allow safe usage when
* localStorage is not available.
*/
export default class Storage {
/**
* Set a value in localStorage. For efficiency, this method will remove the
* value if it is undefined.
*
* @param key The key to set under.
* @param value The value to set
*/
static set<T>(key: string, value: T) {
try {
if (value === undefined) {
this.remove(key);
} else {
localStorage.setItem(key, JSON.stringify(value));
}
} catch (error) {
// no-op Safari private mode
}
}
/**
* Get a value from localStorage.
*
* @param key The key to get.
* @returns The value or undefined if it doesn't exist.
*/
static get(key: string) {
try {
const value = localStorage.getItem(key);
if (typeof value === "string") {
return JSON.parse(value);
}
} catch (error) {
// no-op Safari private mode
}
return undefined;
}
/**
* Remove a value from localStorage.
*
* @param key The key to remove.
*/
static remove(key: string) {
try {
localStorage.removeItem(key);
} catch (error) {
// no-op Safari private mode
}
}
}

View File

@@ -52,8 +52,9 @@ import visualbasic from "refractor/lang/visual-basic";
import yaml from "refractor/lang/yaml";
import zig from "refractor/lang/zig";
import { UserPreferences } from "@shared/types";
import { Dictionary } from "~/hooks/useDictionary";
import { UserPreferences } from "../../types";
import Storage from "../../utils/Storage";
import toggleBlockType from "../commands/toggleBlockType";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
@@ -220,7 +221,7 @@ export default class CodeFence extends Node {
commands({ type, schema }: { type: NodeType; schema: Schema }) {
return (attrs: Record<string, any>) =>
toggleBlockType(type, schema.nodes.paragraph, {
language: localStorage?.getItem(PERSISTENCE_KEY) || DEFAULT_LANGUAGE,
language: Storage.get(PERSISTENCE_KEY, DEFAULT_LANGUAGE),
...attrs,
});
}
@@ -302,7 +303,7 @@ export default class CodeFence extends Node {
view.dispatch(transaction);
localStorage?.setItem(PERSISTENCE_KEY, language);
Storage.set(PERSISTENCE_KEY, language);
}
};
@@ -364,7 +365,7 @@ export default class CodeFence extends Node {
inputRules({ type }: { type: NodeType }) {
return [
textblockTypeInputRule(/^```$/, type, () => ({
language: localStorage?.getItem(PERSISTENCE_KEY) || DEFAULT_LANGUAGE,
language: Storage.get(PERSISTENCE_KEY, DEFAULT_LANGUAGE),
})),
];
}

View File

@@ -8,6 +8,7 @@ import {
} from "prosemirror-model";
import { Plugin, Selection } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
import Storage from "../../utils/Storage";
import backspaceToParagraph from "../commands/backspaceToParagraph";
import splitHeading from "../commands/splitHeading";
import toggleBlockType from "../commands/toggleBlockType";
@@ -151,9 +152,9 @@ export default class Heading extends Node {
const persistKey = headingToPersistenceKey(node, this.editor.props.id);
if (collapsed) {
localStorage?.setItem(persistKey, "collapsed");
Storage.set(persistKey, "collapsed");
} else {
localStorage?.removeItem(persistKey);
Storage.remove(persistKey);
}
view.dispatch(transaction);

View File

@@ -1,6 +1,7 @@
import { Plugin } from "prosemirror-state";
import { findBlockNodes } from "prosemirror-utils";
import { Decoration, DecorationSet } from "prosemirror-view";
import Storage from "../../utils/Storage";
import Extension from "../lib/Extension";
import { headingToPersistenceKey } from "../lib/headingToSlug";
import findCollapsedNodes from "../queries/findCollapsedNodes";
@@ -40,7 +41,7 @@ export default class Folding extends Extension {
block.node,
this.editor.props.id
);
const persistedState = localStorage?.getItem(persistKey);
const persistedState = Storage.get(persistKey);
if (persistedState === "collapsed") {
tr.setNodeMarkup(block.pos, undefined, {

95
shared/utils/Storage.ts Normal file
View File

@@ -0,0 +1,95 @@
/**
* Storage is a wrapper class for localStorage that allow safe usage when
* localStorage is not available.
*/
class Storage {
interface: typeof localStorage | MemoryStorage;
public constructor() {
try {
localStorage.setItem("test", "test");
localStorage.removeItem("test");
this.interface = localStorage;
} catch (_err) {
this.interface = new MemoryStorage();
}
}
/**
* Set a value in storage. For efficiency, this method will remove the
* value if it is undefined.
*
* @param key The key to set under.
* @param value The value to set
*/
public set<T>(key: string, value: T) {
try {
if (value === undefined) {
this.remove(key);
} else {
this.interface.setItem(key, JSON.stringify(value));
}
} catch (_err) {
// Ignore errors
}
}
/**
* Get a value from storage.
*
* @param key The key to get.
* @param fallback The fallback value if the key doesn't exist.
* @returns The value or undefined if it doesn't exist.
*/
public get(key: string, fallback?: any) {
try {
const value = this.interface.getItem(key);
if (typeof value === "string") {
return JSON.parse(value);
}
} catch (_err) {
// Ignore errors
}
return fallback;
}
/**
* Remove a value from storage.
*
* @param key The key to remove.
*/
public remove(key: string) {
try {
this.interface.removeItem(key);
} catch (_err) {
// Ignore errors
}
}
}
/**
* MemoryStorage is a simple in-memory storage implementation that is used
* when localStorage is not available.
*/
class MemoryStorage {
private data = {};
getItem(key: string) {
return this.data[key] || null;
}
setItem(key: string, value: any) {
return (this.data[key] = String(value));
}
removeItem(key: string) {
return delete this.data[key];
}
clear() {
return (this.data = {});
}
}
export default new Storage();