chore(dev): app boilerplate

Electron, React, Vite app boilerplate

- license header
- pre-commit and commit-msg hooks
- storybook
- fix windows tests;
- fix linux build
- CI setup
- persistent store with electron-store and safeStorage
- localization with react-intl

Refs:
- https://github.com/haveno-dex/haveno-ui/projects/1#card-81001746
- https://github.com/haveno-dex/haveno-ui/projects/1#card-81001745

Authored-by: schowdhuri
Reviewed-by: localredhead
This commit is contained in:
Subir 2022-04-23 04:32:54 +05:30 committed by Subir
parent 3a379a7c55
commit a9893aa853
81 changed files with 16560 additions and 0 deletions

26
packages/preload/contracts.d.ts vendored Normal file
View file

@ -0,0 +1,26 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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.
// =============================================================================
/* eslint-disable @typescript-eslint/consistent-type-imports */
interface Exposed {
readonly nodeCrypto: Readonly<typeof import("./src/nodeCrypto").nodeCrypto>;
readonly versions: Readonly<typeof import("./src/versions").versions>;
readonly electronStore: Readonly<typeof import("./src/store").store>;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Window extends Exposed {}

View file

@ -0,0 +1,32 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 { contextBridge } from "electron";
/**
* Typesafe wrapper for `electron.contextBridge.exposeInMainWorld`.
* Guarantees that all exposed APIs will comply with contracts.
* @param key The key to inject the API onto window with. The API will be accessible on window[apiKey].
* @param api Your API
*
* @see https://www.electronjs.org/docs/latest/api/context-bridge#contextbridgeexposeinmainworldapikey-api
*/
export function exposeInMainWorld<T extends keyof Exposed & string>(
key: T,
api: Exposed[T]
) {
return contextBridge.exposeInMainWorld(key, api);
}

View file

@ -0,0 +1,23 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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.
// =============================================================================
/**
* @module preload
*/
import "./nodeCrypto";
import "./versions";
import "./store";

View file

@ -0,0 +1,27 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 { type BinaryLike, createHash } from "crypto";
import { exposeInMainWorld } from "./exposeInMainWorld";
function sha256sum(data: BinaryLike) {
return createHash("sha256").update(data).digest("hex");
}
// Export for types in contracts.d.ts
export const nodeCrypto = { sha256sum } as const;
exposeInMainWorld("nodeCrypto", nodeCrypto);

View file

@ -0,0 +1,27 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 { ipcRenderer } from "electron";
import { exposeInMainWorld } from "./exposeInMainWorld";
import type { UserInfoInputType } from "./types";
// Export for types in contracts.d.ts
export const store = {
storeUserinfo: async (data?: UserInfoInputType) =>
ipcRenderer.invoke("store:userinfo", data),
};
exposeInMainWorld("electronStore", store);

View file

@ -0,0 +1,17 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 "./store";

View file

@ -0,0 +1,64 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 type { Schema } from "electron-store";
export enum StoreKeys {
UserInfo = "UserInfo",
Permissions = "Permissions",
}
// TS types for StoreSchema
export interface IStoreSchema {
[StoreKeys.UserInfo]: IUserInfo;
[StoreKeys.Permissions]: Array<IUserPermission>;
}
export interface IUserInfo {
username: string;
password: Buffer;
}
export type UserInfoInputType = Omit<IUserInfo, "password"> & {
password: string;
};
export interface IUserPermission {
name: string;
}
// this schema is used by electron-store
// must mirror IStoreSchema
export const StoreSchema: Schema<IStoreSchema> = {
[StoreKeys.UserInfo]: {
type: "object",
required: [],
properties: {
username: { type: "string" },
},
},
[StoreKeys.Permissions]: {
type: "array",
default: [],
items: {
type: "object",
required: [],
properties: {
name: { type: "string" },
},
},
},
};

View file

@ -0,0 +1,22 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 { exposeInMainWorld } from "./exposeInMainWorld";
// Export for types in contracts.d.ts
export const versions = process.versions;
exposeInMainWorld("versions", versions);

View file

@ -0,0 +1,47 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 { createHash } from "crypto";
import { afterEach, expect, test, vi } from "vitest";
const exposeInMainWorldMock = vi.fn();
vi.mock("electron", () => ({
contextBridge: { exposeInMainWorld: exposeInMainWorldMock },
}));
afterEach(() => {
vi.clearAllMocks();
});
test("versions", async () => {
await import("../src/versions");
expect(exposeInMainWorldMock).toBeCalledTimes(1);
expect(exposeInMainWorldMock).lastCalledWith("versions", process.versions);
});
test("nodeCrypto", async () => {
await import("../src/nodeCrypto");
expect(exposeInMainWorldMock).toBeCalledTimes(1);
expect(exposeInMainWorldMock.mock.calls[0][0]).toBe("nodeCrypto");
expect(exposeInMainWorldMock.mock.calls[0][1]).toHaveProperty("sha256sum");
const data = "rawData";
const expectedHash = createHash("sha256").update(data).digest("hex");
expect(exposeInMainWorldMock.mock.calls[0][1].sha256sum(data)).toBe(
expectedHash
);
});

View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"sourceMap": false,
"moduleResolution": "Node",
"skipLibCheck": true,
"strict": true,
"isolatedModules": true,
"types": ["node"],
"baseUrl": "."
},
"include": ["src/**/*.ts", "contracts.d.ts", "../../types/**/*.d.ts"],
"exclude": ["**/*.spec.ts", "**/*.test.ts"]
}

View file

@ -0,0 +1,54 @@
// =============================================================================
// Copyright 2022 Haveno
//
// 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 { chrome } from "../../.electron-vendors.cache.json";
import { builtinModules } from "module";
const PACKAGE_ROOT = __dirname;
/**
* @type {import('vite').UserConfig}
* @see https://vitejs.dev/config/
*/
const config = {
mode: process.env.MODE,
root: PACKAGE_ROOT,
envDir: process.cwd(),
build: {
sourcemap: "inline",
target: `chrome${chrome}`,
outDir: "dist",
assetsDir: ".",
minify: process.env.MODE !== "development",
lib: {
entry: "src/index.ts",
formats: ["cjs"],
},
rollupOptions: {
external: [
"electron",
...builtinModules.flatMap((p) => [p, `node:${p}`]),
],
output: {
entryFileNames: "[name].cjs",
},
},
emptyOutDir: true,
brotliSize: false,
},
};
export default config;