chore: support localstorage APIs using appsmith store functions (#19789)

## Description
Most JS libraries are written with the intent to run of browsers main
thread and not a web worker. JS libraries relies on the support for
local storage APIs fail because web worker do not have access to local
storage APIs. Examples of libraries that rely on localStorage include
Mixpanel-browser, Supabasev2 etc.

- Mocks localStorage API by using the respective store operation under
the hood.
- Autocomplete will not suggest localStorage APIs to promote the use of
appsmith store functions.

Fixes #19792 

### Type of change
- Chore (housekeeping or task changes that don't impact user perception)

### How Has This Been Tested?
- Manual
1. Instal supobase -
https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.4.0/dist/umd/supabase.min.js
and verify that its working properly
2. Instal recommend libraries and verify that it is getting installed
properly


> Add Testsmith test cases links that relate to this PR

### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)


## Checklist:
### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
arunvjn 2023-01-19 10:01:26 +05:30 committed by GitHub
parent 98a29b890f
commit e4a84b7c23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 4 deletions

View File

@ -4,7 +4,6 @@ import {
createEvaluationContext,
EvalContext,
} from "workers/Evaluation/evaluate";
import uniqueId from "lodash/uniqueId";
import { MessageType } from "utils/MessageUtil";
import {
addDataTreeToContext,

View File

@ -0,0 +1,30 @@
import get from "lodash/get";
export default function initLocalStorage(ctx: typeof globalThis) {
function getItem(key: string) {
//@ts-expect-error no types
return get(ctx.appsmith.store, key);
}
function setItem(key: string, value: any) {
//@ts-expect-error no types
ctx.storeValue(key, value);
}
function removeItem(key: string) {
//@ts-expect-error no types
ctx.removeValue(key);
}
function clear() {
//@ts-expect-error no types
ctx.clearStore();
}
const localStorage = {
getItem,
setItem,
removeItem,
clear,
};
Object.defineProperty(ctx, "localStorage", {
enumerable: false,
value: localStorage,
});
}

View File

@ -0,0 +1,102 @@
import { addPlatformFunctionsToEvalContext } from "ce/workers/Evaluation/Actions";
import { ENTITY_TYPE } from "design-system";
import { PluginType } from "entities/Action";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { createEvaluationContext } from "workers/Evaluation/evaluate";
import initLocalStorage from "../LocalStorage";
describe("Tests localStorage implementation in worker", () => {
const dataTree: DataTree = {
action1: {
actionId: "123",
pluginId: "",
data: {},
config: {},
datasourceUrl: "",
pluginType: PluginType.API,
dynamicBindingPathList: [],
name: "action1",
bindingPaths: {},
reactivePaths: {},
isLoading: false,
run: {},
clear: {},
responseMeta: { isExecutionSuccess: false },
ENTITY_TYPE: ENTITY_TYPE.ACTION,
dependencyMap: {},
logBlackList: {},
},
};
const workerEventMock = jest.fn();
self.postMessage = workerEventMock;
self.ALLOW_ASYNC = true;
const evalContext = createEvaluationContext({
dataTree,
resolvedFunctions: {},
isTriggerBased: true,
context: {},
});
addPlatformFunctionsToEvalContext(evalContext);
initLocalStorage(evalContext as any);
it("setItem()", () => {
const key = "some";
const value = "thing";
jest.useFakeTimers();
evalContext.localStorage.setItem(key, value);
jest.runAllTimers();
expect(workerEventMock).lastCalledWith({
messageType: "DEFAULT",
body: {
data: [
{
payload: {
key: "some",
value: "thing",
persist: true,
},
type: "STORE_VALUE",
},
],
method: "PROCESS_STORE_UPDATES",
},
});
});
it("getItem()", () => {
expect(evalContext.localStorage.getItem("some")).toBe("thing");
});
it("removeItem()", () => {
evalContext.localStorage.removeItem("some");
jest.runAllTimers();
expect(workerEventMock).lastCalledWith({
messageType: "DEFAULT",
body: {
data: [
{
payload: {
key: "some",
},
type: "REMOVE_VALUE",
},
],
method: "PROCESS_STORE_UPDATES",
},
});
});
it("clear()", () => {
evalContext.localStorage.clear();
jest.runAllTimers();
expect(workerEventMock).lastCalledWith({
messageType: "DEFAULT",
body: {
data: [
{
payload: null,
type: "CLEAR_STORE",
},
],
method: "PROCESS_STORE_UPDATES",
},
});
});
});

View File

@ -20,7 +20,7 @@ export function initStoreFns(ctx: typeof globalThis) {
persist,
},
};
set(self, ["appsmith", "store", key], value);
set(ctx, ["appsmith", "store", key], value);
triggerCollector.collect(requestPayload);
return Promise.resolve({});
}
@ -33,14 +33,14 @@ export function initStoreFns(ctx: typeof globalThis) {
},
};
//@ts-expect-error no types for store
delete self.appsmith.store[key];
delete ctx.appsmith.store[key];
triggerCollector.collect(requestPayload);
return Promise.resolve({});
}
function clearStore() {
//@ts-expect-error no types for store
self.appsmith.store = {};
ctx.appsmith.store = {};
triggerCollector.collect({
type: "CLEAR_STORE",
payload: null,

View File

@ -6,6 +6,7 @@ import overrideTimeout from "../TimeoutOverride";
import { EvalWorkerSyncRequest } from "../types";
import userLogs from "../UserLog";
import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions";
import initLocalStorage from "../fns/LocalStorage";
export default function() {
const libraries = resetJSLibraries();
@ -26,6 +27,7 @@ export default function() {
interceptAndOverrideHttpRequest();
setupDOM();
addPlatformFunctionsToEvalContext(self);
initLocalStorage(self);
return true;
}