mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-12 07:41:09 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
build
|
||||
node_modules
|
||||
release
|
||||
@@ -0,0 +1,17 @@
|
||||
# @pezkuwi/apps-electron
|
||||
|
||||
Desktop Pezkuwi apps client for Windows, Mac and Linux.
|
||||
|
||||
## Installation
|
||||
|
||||
[Download here](https://github.com/pezkuwi-js/apps/releases/latest) latest versions for Windows, Mac and Linux.
|
||||
|
||||
## Development and testing
|
||||
|
||||
Contributions are welcome!
|
||||
|
||||
Follow steps described [here](https://github.com/pezkuwi-js/apps#development) to setup the project.
|
||||
|
||||
* Run `yarn start:electron` to start the app in development mode. You will possibly see the `Not Found / 404` message. It's ok, just wait for the build to finish and refresh pressing `Ctrl+R`.
|
||||
* Run `yarn test` to run tests
|
||||
* Run `yarn packElectron:(mac|linux|windows)` with the OS you want to build for to create the app executable. Find the packages in `packages/apps-electron/release`.
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,21 @@
|
||||
---
|
||||
productName: Polkadot-JS Apps
|
||||
artifactName: Polkadot-JS-Apps-${version}.${ext}
|
||||
files:
|
||||
- "./build"
|
||||
- "./assets"
|
||||
- "./appleEntitlements"
|
||||
appId: com.polkadotjs.polkadotjs-apps
|
||||
linux:
|
||||
executableName: polkadot-apps
|
||||
mac:
|
||||
artifactName: Polkadot-JS-Apps-mac-${version}.${ext}
|
||||
category: public.app-category.finance
|
||||
entitlements: "./packages/apps-electron/appleEntitlements/entitlements.mac.plist"
|
||||
extendInfo:
|
||||
NSCameraUsageDescription: "This app requires camera access to capture account data on imports"
|
||||
hardenedRuntime: true
|
||||
directories:
|
||||
buildResources: "./assets"
|
||||
output: "./release"
|
||||
afterSign: electron-builder-notarize
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"author": "Dijital Kurdistan Tech Institute <info@pezkuwichain.io>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-apps/issues",
|
||||
"description": "An Apps portal into the Pezkuwi network",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-apps/tree/master/packages/apps-electron#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/apps-electron",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"directory": "packages/apps-electron",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-apps.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "0.168.2-4-x",
|
||||
"main": "build/electron.js",
|
||||
"dependencies": {
|
||||
"electron-log": "^5.0.1",
|
||||
"electron-updater": "^6.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/dev": "^0.85.2",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"electron": "28.0.0",
|
||||
"electron-builder": "24.10.0",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"html-webpack-plugin": "^5.5.4",
|
||||
"tmp": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import type { KeyringJson } from '@pezkuwi/ui-keyring/types';
|
||||
|
||||
export interface AccountStoreApi {
|
||||
all: () => Promise<{ key: string, value: KeyringJson }[]>
|
||||
get: (key: string) => Promise<KeyringJson>
|
||||
remove: (key: string) => Promise<void>
|
||||
set: (key: string, value: KeyringJson) => Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountStoreApi } from './account-store-api.js';
|
||||
|
||||
export interface ElectronMainApi {
|
||||
accountStore: AccountStoreApi
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ElectronMainApi } from './electron-main-api.js';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ElectronMain: ElectronMainApi
|
||||
}
|
||||
}
|
||||
|
||||
export const electronMainApi = window.ElectronMain;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AppUpdater } from 'electron-updater';
|
||||
|
||||
export async function setupAutoUpdater (): Promise<void> {
|
||||
const { autoUpdater } = await import('electron-updater');
|
||||
|
||||
await setLogger(autoUpdater);
|
||||
autoUpdater.checkForUpdatesAndNotify().catch(console.error);
|
||||
}
|
||||
|
||||
async function setLogger (autoUpdater: AppUpdater): Promise<void> {
|
||||
const log = await import('electron-log');
|
||||
|
||||
log.transports.file.level = 'debug';
|
||||
autoUpdater.logger = log;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HeadersReceivedResponse } from 'electron';
|
||||
|
||||
import { session } from 'electron';
|
||||
|
||||
export function setupContentSecurityPolicy (_: string): void {
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, respond: (response: HeadersReceivedResponse) => void) => {
|
||||
respond({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [
|
||||
"default-src 'self';" +
|
||||
" style-src-elem 'self' https://fonts.googleapis.com/css 'unsafe-inline';" +
|
||||
" font-src data: 'self' https://fonts.gstatic.com;" +
|
||||
" style-src 'unsafe-inline';" +
|
||||
" connect-src 'self' wss: ws:;" +
|
||||
" img-src 'self' data:;" +
|
||||
// react-qr-reader uses an embedded blob
|
||||
" worker-src 'self' blob: filesystem:;" +
|
||||
// unsafe-eval is needed for the WASM content - same as the extension
|
||||
// script hashes here are for the window.top script (not technically needed)
|
||||
" script-src 'self' 'unsafe-eval' 'sha256-02/ejyoV/iwRdJ4NAsxjzF6WVUtLMPM6Nv96EbAm6u8=' 'sha256-wW/WsLudCDaPo/ibpeK0KslHqYpCzcAKNFxFBXwCHJg='"
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
import { registerAccountStoreHandlers } from '../main/account-store.js';
|
||||
import { setupAutoUpdater } from './autoUpdater.js';
|
||||
import { setupContentSecurityPolicy } from './contentSecurityPolicy.js';
|
||||
import { createWindow } from './window.js';
|
||||
|
||||
const ENV = process.env.NODE_ENV || 'production';
|
||||
|
||||
app.on('web-contents-created', (_, webContents): void => {
|
||||
webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
|
||||
});
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
.then(async (): Promise<void> => {
|
||||
registerAccountStoreHandlers();
|
||||
setupContentSecurityPolicy(ENV);
|
||||
|
||||
await createWindow(ENV);
|
||||
await setupAutoUpdater();
|
||||
})
|
||||
.catch(console.error);
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BrowserWindow, screen, shell } from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
export function createWindow (environment: string): Promise<unknown> {
|
||||
const { height, width } = screen.getPrimaryDisplay().workAreaSize;
|
||||
|
||||
const win = new BrowserWindow({
|
||||
height,
|
||||
icon: path.join(__dirname, 'icon.png'),
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
width
|
||||
});
|
||||
|
||||
if (environment === 'development') {
|
||||
win.webContents.openDevTools();
|
||||
|
||||
return win.loadURL('http://127.0.0.1:3000/');
|
||||
}
|
||||
|
||||
// Handle attempts to open a new window via window.open()
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
// Open all http/https URLs externally
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
shell.openExternal(url).catch(console.log);
|
||||
|
||||
return { action: 'deny' };
|
||||
}
|
||||
|
||||
return { action: 'allow' };
|
||||
});
|
||||
|
||||
// Handle in-app navigation attempts, such as clicking on <a href="...">
|
||||
win.webContents.on('will-navigate', (event, url) => {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url).catch(console.log);
|
||||
}
|
||||
});
|
||||
|
||||
const mainFilePath = path.resolve(__dirname, 'index.html');
|
||||
|
||||
return win.loadFile(mainFilePath);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// setup these right at front
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import '@pezkuwi/apps/initSettings';
|
||||
import 'semantic-ui-css/semantic.min.css';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import '@pezkuwi/react-components/i18n';
|
||||
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import Root from '@pezkuwi/apps/Root';
|
||||
|
||||
import { electronMainApi } from './api/global-exported-api.js';
|
||||
import { RemoteElectronStore } from './renderer/remote-electron-store.js';
|
||||
|
||||
const rootId = 'root';
|
||||
const rootElement = document.getElementById(rootId);
|
||||
|
||||
if (!rootElement) {
|
||||
throw new Error(`Unable to find element with id '${rootId}'`);
|
||||
}
|
||||
|
||||
const store = new RemoteElectronStore(electronMainApi.accountStore);
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<Root
|
||||
isElectron
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import type { KeyringJson } from '@pezkuwi/ui-keyring/types';
|
||||
import type { IpcMainHandler } from './ipc-main-handler.js';
|
||||
|
||||
import * as tmp from 'tmp';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import { FileStore } from '@pezkuwi/ui-keyring/stores';
|
||||
|
||||
import { accountStoreIpcHandler } from './account-store.js';
|
||||
|
||||
const exampleAccount = (address: string): KeyringJson => ({
|
||||
address,
|
||||
meta: {}
|
||||
});
|
||||
|
||||
describe('Account store', () => {
|
||||
let accountStore: IpcMainHandler;
|
||||
let tmpDir: tmp.DirResult;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||
accountStore = accountStoreIpcHandler(new FileStore(tmpDir.name));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tmpDir.removeCallback();
|
||||
});
|
||||
|
||||
it('all returns empty array at first', () => {
|
||||
expect(accountStore['account-store-all']()).toEqual([]);
|
||||
});
|
||||
|
||||
it('after adding accounts, they become visible', async () => {
|
||||
await accountStore['account-store-set']('1', exampleAccount('a'));
|
||||
await accountStore['account-store-set']('2', exampleAccount('b'));
|
||||
|
||||
expect(accountStore['account-store-all']()).toEqual([{
|
||||
key: '1', value: exampleAccount('a')
|
||||
}, {
|
||||
key: '2', value: exampleAccount('b')
|
||||
}]);
|
||||
});
|
||||
|
||||
it('get returns account if exists', async () => {
|
||||
await accountStore['account-store-set']('1', exampleAccount('a'));
|
||||
expect(await accountStore['account-store-get']('1')).toEqual(exampleAccount('a'));
|
||||
});
|
||||
|
||||
it('get returns null if account does not exist', async () => {
|
||||
// jest.spyOn(console, 'error').mockImplementationOnce(() => { /**/ });
|
||||
|
||||
expect(await accountStore['account-store-get']('1')).toEqual(null);
|
||||
});
|
||||
|
||||
it('account disappears from list after it is removed', async () => {
|
||||
// jest.spyOn(console, 'error').mockImplementationOnce(() => { /**/ });
|
||||
|
||||
await accountStore['account-store-set']('1', exampleAccount('a'));
|
||||
await accountStore['account-store-remove']('1');
|
||||
|
||||
expect(await accountStore['account-store-get']('1')).toEqual(null);
|
||||
expect(accountStore['account-store-all']()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import type { KeyringJson } from '@pezkuwi/ui-keyring/types';
|
||||
import type { IpcMainHandler } from './ipc-main-handler.js';
|
||||
|
||||
import electron from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import { FileStore } from '@pezkuwi/ui-keyring/stores';
|
||||
|
||||
import { registerIpcHandler } from './register-ipc-handler.js';
|
||||
|
||||
const ACCOUNTS_SUBFOLDER = 'pezkuwi-accounts';
|
||||
|
||||
function safeWriteKey (key: string) {
|
||||
return key.replace(/:/g, '-');
|
||||
}
|
||||
|
||||
function safeReadKey (key: string) {
|
||||
return key.replace(/-/g, ':');
|
||||
}
|
||||
|
||||
export const accountStoreIpcHandler = (fileStore: FileStore): IpcMainHandler => ({
|
||||
'account-store-all': () => {
|
||||
let result: { key: string, value: KeyringJson }[] = [];
|
||||
|
||||
const collect = (key: string, value: KeyringJson) => {
|
||||
result = [...result, { key: safeReadKey(key), value }];
|
||||
};
|
||||
|
||||
fileStore.all(collect);
|
||||
|
||||
return result;
|
||||
},
|
||||
'account-store-get': async (key: string) => new Promise((resolve) => {
|
||||
try {
|
||||
fileStore.get(safeWriteKey(key), resolve);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
}),
|
||||
'account-store-remove': async (key: string) => new Promise((resolve) =>
|
||||
fileStore.remove(safeWriteKey(key), () => resolve(undefined))
|
||||
),
|
||||
'account-store-set': async (key: string, value: KeyringJson) => new Promise((resolve) =>
|
||||
fileStore.set(safeWriteKey(key), value, () => resolve(undefined))
|
||||
)
|
||||
});
|
||||
|
||||
export const registerAccountStoreHandlers = (): void => {
|
||||
const defaultStorePath = path.join(electron.app.getPath('userData'), ACCOUNTS_SUBFOLDER);
|
||||
const fileStore = new FileStore(defaultStorePath);
|
||||
|
||||
registerIpcHandler(accountStoreIpcHandler(fileStore));
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type IpcMainHandler = Record<string, (...args: any[]) => unknown>;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { IpcMainHandler } from './ipc-main-handler.js';
|
||||
|
||||
import electron from 'electron';
|
||||
|
||||
export const registerIpcHandler = (ipcHandler: IpcMainHandler): void => {
|
||||
for (const [channel, listener] of Object.entries(ipcHandler)) {
|
||||
electron.ipcMain.handle(channel, (_, ...args: unknown[]) => {
|
||||
return listener(...args);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import type { KeyringJson } from '@pezkuwi/ui-keyring/types';
|
||||
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
contextBridge.exposeInMainWorld('ElectronMain', {
|
||||
accountStore: {
|
||||
all: () => ipcRenderer.invoke('account-store-all'),
|
||||
get: (key: string) => ipcRenderer.invoke('account-store-get', key),
|
||||
remove: (key: string) => ipcRenderer.invoke('account-store-remove', key),
|
||||
set: (key: string, value: KeyringJson) => ipcRenderer.invoke('account-store-set', key, value)
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import type { KeyringJson } from '@pezkuwi/ui-keyring/types';
|
||||
|
||||
import { RemoteElectronStore } from './remote-electron-store.js';
|
||||
|
||||
describe('Remote Electron Store', () => {
|
||||
const accountStore = {
|
||||
all: jest.fn(),
|
||||
get: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
set: jest.fn()
|
||||
};
|
||||
const remoteStore = new RemoteElectronStore(accountStore);
|
||||
|
||||
beforeEach(() => {
|
||||
accountStore.all.mockClear();
|
||||
accountStore.get.mockClear();
|
||||
accountStore.remove.mockClear();
|
||||
accountStore.set.mockClear();
|
||||
});
|
||||
|
||||
describe('all', () => {
|
||||
it('calls callback for each returned account', async () => {
|
||||
accountStore.all.mockResolvedValue([{
|
||||
key: 1,
|
||||
value: 'a'
|
||||
}, {
|
||||
key: 2,
|
||||
value: 'b'
|
||||
}]);
|
||||
const cb = jest.fn();
|
||||
|
||||
remoteStore.all(cb);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(cb).toHaveBeenNthCalledWith(1, 1, 'a');
|
||||
expect(cb).toHaveBeenNthCalledWith(2, 2, 'b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('calls callback with returned account', async () => {
|
||||
accountStore.get.mockResolvedValue('a');
|
||||
const cb = jest.fn();
|
||||
|
||||
remoteStore.get('1', cb);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(accountStore.get).toHaveBeenCalledWith('1');
|
||||
expect(cb).toHaveBeenCalledWith('a');
|
||||
});
|
||||
|
||||
it('calls callback with null if no accounts found', async () => {
|
||||
accountStore.get.mockResolvedValue(null);
|
||||
const cb = jest.fn();
|
||||
|
||||
remoteStore.get('1', cb);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('calls callback after success', async () => {
|
||||
accountStore.remove.mockResolvedValue(null);
|
||||
const cb = jest.fn();
|
||||
|
||||
remoteStore.remove('1', cb);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(accountStore.remove).toHaveBeenCalledWith('1');
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', () => {
|
||||
it('calls callback after success', async () => {
|
||||
accountStore.set.mockResolvedValue(null);
|
||||
const cb = jest.fn();
|
||||
|
||||
remoteStore.set('1', 'a' as unknown as KeyringJson, cb);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(accountStore.set).toHaveBeenCalledWith('1', 'a');
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Warned on by nodenext resolution (while package does build in bundler mode)
|
||||
import type { KeyringJson, KeyringStore } from '@pezkuwi/ui-keyring/types';
|
||||
import type { AccountStoreApi } from '../api/account-store-api.js';
|
||||
|
||||
export class RemoteElectronStore implements KeyringStore {
|
||||
readonly #accountStore: AccountStoreApi;
|
||||
|
||||
constructor (accountStore: AccountStoreApi) {
|
||||
this.#accountStore = accountStore;
|
||||
}
|
||||
|
||||
all (cb: (key: string, value: KeyringJson) => void): void {
|
||||
this.#accountStore.all()
|
||||
.then((result: { key: string, value: KeyringJson }[]) => result?.forEach(({ key, value }) => cb(key, value)))
|
||||
.catch((e: Error) => {
|
||||
throw new Error(`error getting all accounts: ${e.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
get (key: string, cb: (value: KeyringJson) => void): void {
|
||||
this.#accountStore.get(key)
|
||||
.then(cb).catch((e: Error) => {
|
||||
throw new Error(`error storing account: ${e.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
remove (key: string, cb: (() => void) | undefined): void {
|
||||
this.#accountStore.remove(key).then(cb).catch((e: Error) => {
|
||||
throw new Error(`error removing account: ${e.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
set (key: string, value: KeyringJson, cb: (() => void) | undefined): void {
|
||||
this.#accountStore.set(key, value).then(cb).catch((e: Error) => {
|
||||
throw new Error(`error saving account: ${e.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
/* Since this is a forced-by-electron-CJS-module, we have to switch this off */
|
||||
"verbatimModuleSyntax": false
|
||||
},
|
||||
"exclude": [
|
||||
"webpack.*.cjs"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../apps/tsconfig.build.json" },
|
||||
{ "path": "../react-components/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2017-2025 @polkadot/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
function createWebpack () {
|
||||
return [
|
||||
{
|
||||
entry: {
|
||||
electron: './src/electron',
|
||||
preload: './src/preload.ts'
|
||||
},
|
||||
mode: 'production',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
include: /node_modules/,
|
||||
test: /\.mjs$/,
|
||||
type: 'javascript/auto'
|
||||
},
|
||||
{
|
||||
exclude: /(node_modules)/,
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('ts-loader'),
|
||||
options: {
|
||||
configFile: 'tsconfig.webpack.json',
|
||||
transpileOnly: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '/build')
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({ patterns: [{ from: 'assets' }] })
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@polkadot/hw-ledger-transports': require.resolve('@polkadot/hw-ledger-transports/node')
|
||||
},
|
||||
extensionAlias: {
|
||||
'.js': ['.ts', '.tsx', '.js']
|
||||
},
|
||||
extensions: ['.js', '.jsx', '.json', '.mjs', '.ts', '.tsx']
|
||||
},
|
||||
target: 'electron-main'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = createWebpack();
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2025 @polkadot/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const path = require('path');
|
||||
const { merge } = require('webpack-merge');
|
||||
|
||||
const baseConfig = require('../apps/webpack.base.cjs');
|
||||
|
||||
const context = __dirname;
|
||||
|
||||
module.exports = merge(
|
||||
baseConfig(context, 'development'),
|
||||
{
|
||||
plugins: [
|
||||
// It must be placed before HtmlWebpackPlugin
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
from: '../apps/public',
|
||||
globOptions: {
|
||||
dot: true,
|
||||
ignore: ['**/index.html']
|
||||
}
|
||||
}]
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
PAGE_TITLE: 'Polkadot/Substrate Portal',
|
||||
minify: false,
|
||||
template: path.join(context, '../apps/public/index.html')
|
||||
})
|
||||
],
|
||||
target: 'web'
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user