From 94cbd9f8c494de1bb737c24ec694c130e787d8b6 Mon Sep 17 00:00:00 2001 From: Benoit Devos Date: Tue, 12 Mar 2024 18:04:49 +0100 Subject: [PATCH 1/2] feat: create LogionClient from Environment. logion-network/logion-internal#1099 --- README.md | 5 +++ src/__mocks__/LogionClientMock.ts | 4 ++ src/config/development-env.json.sample | 5 +++ src/config/index.tsx | 7 ++-- src/loc/CertificateAndLimits.tsx | 5 +-- src/loc/ImportItems.tsx | 56 +++++++++++++------------ src/logion-chain/LogionChainContext.tsx | 34 ++++++++------- 7 files changed, 69 insertions(+), 47 deletions(-) create mode 100644 src/config/development-env.json.sample diff --git a/README.md b/README.md index 1b5b2851..89ca8fa6 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ For the most recent version and how to install yarn, please refer to [yarn](http yarn install ``` +## Target an environment +A config file `development.json` is required under [src/config/](src/config/). +* To target a locally running node and directory, or fine-tune the config, it's recommended to copy and/or adapt from [development.json.sample](src/config/development.json.sample). +* To target a deployed environment (i.e. DEV, TEST or MVP), it's recommended to copy and/or adapt from [development-env.json.sample](src/config/development-env.json.sample). + ## Usage Use below command in order to connect to a locally running infrastructure diff --git a/src/__mocks__/LogionClientMock.ts b/src/__mocks__/LogionClientMock.ts index 051e1718..b40c4766 100644 --- a/src/__mocks__/LogionClientMock.ts +++ b/src/__mocks__/LogionClientMock.ts @@ -83,6 +83,10 @@ export class LogionClient { throw new Error(); } } + + config = { + rpcEndpoints: [ "ws://localhost:9944", "ws://localhost:9945", "ws://localhost:9946" ] + }; } export class AccountTokens { diff --git a/src/config/development-env.json.sample b/src/config/development-env.json.sample new file mode 100644 index 00000000..6b425b38 --- /dev/null +++ b/src/config/development-env.json.sample @@ -0,0 +1,5 @@ +{ + "APP_NAME": "Logion Wallet DEV", + "environment": "DEV" + "crossmintApiKey": "", +} diff --git a/src/config/index.tsx b/src/config/index.tsx index 713392d9..76e44066 100644 --- a/src/config/index.tsx +++ b/src/config/index.tsx @@ -1,13 +1,15 @@ +import { EnvironmentString } from "@logion/client"; + export interface Node { socket: string; peerId: string; } export interface ConfigType { + environment: EnvironmentString | undefined, APP_NAME: string, DEVELOPMENT_KEYRING: boolean, PROVIDER_SOCKET?: string, - RPC: object, directory: string, edgeNodes: Node[], crossmintApiKey: string, @@ -16,10 +18,9 @@ export interface ConfigType { } export const DEFAULT_CONFIG: ConfigType = { + environment: undefined, APP_NAME: "Logion Wallet", DEVELOPMENT_KEYRING: true, - RPC: { - }, directory: "", edgeNodes: [], crossmintApiKey: "", diff --git a/src/loc/CertificateAndLimits.tsx b/src/loc/CertificateAndLimits.tsx index 75bfb9f9..f0e23d39 100644 --- a/src/loc/CertificateAndLimits.tsx +++ b/src/loc/CertificateAndLimits.tsx @@ -9,7 +9,6 @@ import Dialog from '../common/Dialog'; import Icon from '../common/Icon'; import { useLogionChain } from '../logion-chain'; import { fullCertificateUrl } from '../PublicPaths'; -import config from '../config'; import NewTabLink from '../common/NewTabLink'; import StaticLabelValue from '../common/StaticLabelValue'; @@ -39,7 +38,7 @@ export interface Props { export default function CertificateAndLimits(props: Props) { const { loc } = props; - const { api } = useLogionChain(); + const { api, client } = useLogionChain(); const { backendConfig } = useCommonContext(); const [ dateLimit, setDateLimit ] = useState(); @@ -141,7 +140,7 @@ export default function CertificateAndLimits(props: Props) { { config.edgeNodes[0].socket }

+

{ client?.config.rpcEndpoints[0] }

} /> diff --git a/src/loc/ImportItems.tsx b/src/loc/ImportItems.tsx index dfa1ace3..6feb1680 100644 --- a/src/loc/ImportItems.tsx +++ b/src/loc/ImportItems.tsx @@ -8,6 +8,7 @@ import { LogionClassification, SpecificLicense, CreativeCommons, + LogionClientConfig, } from "@logion/client"; import { Fees, Hash, Lgnt } from '@logion/node-api'; import { useCallback, useMemo, useState } from "react"; @@ -28,7 +29,6 @@ import './ImportItems.css'; import { CsvItem, readItemsCsv } from "./ImportCsvReader"; import Alert from "src/common/Alert"; import { UUID } from "@logion/node-api"; -import config from "../config"; import EstimatedFees from "./fees/EstimatedFees"; import { BrowserFile } from "@logion/client-browser"; import { Call, CallBatch, CallCallback } from "src/logion-chain/LogionChainContext"; @@ -36,7 +36,7 @@ import ExtrinsicSubmissionStateView from "src/ExtrinsicSubmissionStateView"; export default function ImportItems() { const { width } = useResponsiveContext(); - const { signer, submitCallBatch, extrinsicSubmissionState, clearSubmissionState } = useLogionChain(); + const { signer, submitCallBatch, extrinsicSubmissionState, clearSubmissionState, client } = useLogionChain(); const { colorTheme } = useCommonContext(); const { refresh, locState } = useUserLocContext(); @@ -48,29 +48,31 @@ export default function ImportItems() { const [ itemToSubmit, setItemToSubmit ] = useState(); const readCsvFile = useCallback(async (file: File) => { - const collection = locState as ClosedCollectionLoc; - const acceptsUpload = collectionAcceptsUpload(collection); - clearSubmissionState(); - - const result = await readItemsCsv(file); - if("items" in result) { - const rows = toItems(result.items, acceptsUpload); - - for(const item of rows) { - if(!item.error) { - const existingItem = await collection.getCollectionItem({ itemId: item.id as Hash }); - item.submitted = existingItem !== undefined; - item.success = existingItem !== undefined; - item.upload = shouldUpload(acceptsUpload, existingItem, item.upload); + if (client !== null) { + const collection = locState as ClosedCollectionLoc; + const acceptsUpload = collectionAcceptsUpload(collection); + clearSubmissionState(); + + const result = await readItemsCsv(file); + if ("items" in result) { + const rows = toItems(result.items, acceptsUpload, client.config); + + for (const item of rows) { + if (!item.error) { + const existingItem = await collection.getCollectionItem({ itemId: item.id as Hash }); + item.submitted = existingItem !== undefined; + item.success = existingItem !== undefined; + item.upload = shouldUpload(acceptsUpload, existingItem, item.upload); + } } - } - setItems(rows); - } else { - setCsvReadError(result.error); + setItems(rows); + } else { + setCsvReadError(result.error); + } + setShowImportItems(true); } - setShowImportItems(true); - }, [ clearSubmissionState, locState ]); + }, [ clearSubmissionState, locState, client ]); const itemFees = useCallback(async (item: Item) => { const collection = locState as ClosedCollectionLoc; @@ -437,7 +439,7 @@ function collectionAcceptsUpload(collection: ClosedCollectionLoc): boolean { return collection.data().collectionCanUpload !== undefined && collection.data().collectionCanUpload === true; } -function toItems(csvItems: CsvItem[], collectionAcceptsUpload: boolean): Item[] { +function toItems(csvItems: CsvItem[], collectionAcceptsUpload: boolean, config: LogionClientConfig): Item[] { return csvItems.map(csvItem => { const id = csvItem.id; const displayId = csvItem.displayId; @@ -497,7 +499,7 @@ function toItems(csvItems: CsvItem[], collectionAcceptsUpload: boolean): Item[] let tcValidationResult: TCValidationResult = {} if (error === undefined) { - tcValidationResult = validateTermsAndConditions(csvItem); + tcValidationResult = validateTermsAndConditions(csvItem, config); error = tcValidationResult.tcError; errorType = "validation"; } @@ -531,17 +533,17 @@ interface TCValidationResult { tcError?: string; } -function validateTermsAndConditions(csvItem: CsvItem): TCValidationResult { +function validateTermsAndConditions(csvItem: CsvItem, config: LogionClientConfig): TCValidationResult { try { if (csvItem.termsAndConditionsType === 'logion_classification') { - const logionClassificationLocId = UUID.fromAnyString(config.logionClassification); + const logionClassificationLocId = config.logionClassificationLoc; if (logionClassificationLocId === undefined) { return { tcError: "logion_classification: Logion Classification LOC id not properly configured" } } const logionClassification = LogionClassification.fromDetails(logionClassificationLocId, csvItem.termsAndConditionsParameters); return { logionClassification } } else if (csvItem.termsAndConditionsType === 'CC4.0') { - const ccLocId = UUID.fromAnyString(config.creativeCommons); + const ccLocId = config.creativeCommonsLoc; if (ccLocId === undefined) { return { tcError: "CC4.0: LOC id not properly configured" } } diff --git a/src/logion-chain/LogionChainContext.tsx b/src/logion-chain/LogionChainContext.tsx index 388d4085..edeafcaf 100644 --- a/src/logion-chain/LogionChainContext.tsx +++ b/src/logion-chain/LogionChainContext.tsx @@ -16,12 +16,12 @@ import { InjectedAccount, isExtensionAvailable } from '@logion/extension'; -import { buildApiClass, LogionNodeApiClass, ValidAccountId } from '@logion/node-api'; +import { LogionNodeApiClass, ValidAccountId, UUID } from '@logion/node-api'; import axios, { AxiosInstance } from 'axios'; import React, { useReducer, useContext, Context, Reducer, useEffect, useCallback } from 'react'; import { DateTime } from 'luxon'; -import config, { Node } from '../config'; +import config from '../config'; import Accounts, { buildAccounts, toValidAccountId } from '../common/types/Accounts'; import { clearAll, @@ -33,7 +33,7 @@ import { } from '../common/Storage'; import { getEndpoints, NodeMetadata } from './Connection'; -import { BrowserAxiosFileUploader } from '@logion/client-browser'; +import { BrowserAxiosFileUploader, newLogionClient } from '@logion/client-browser'; type ConsumptionStatus = 'PENDING' | 'STARTING' | 'STARTED'; @@ -79,7 +79,7 @@ export class CallBatch { } readonly jobs: CallBatchJobs; -}; +} export class ExtrinsicSubmissionState { @@ -238,7 +238,6 @@ export interface LogionChainContextType { injectedAccounts: InjectedAccount[] | null, metaMaskAccounts: InjectedAccount[] | null, allAccounts: InjectedAccount[] | null, - edgeNodes: Node[], connectedNodeMetadata: NodeMetadata | null, extensionsEnabled: boolean, client: LogionClient | null, @@ -275,7 +274,6 @@ const initState = (): FullLogionChainContextType => ({ injectedAccounts: null, metaMaskAccounts: null, allAccounts: null, - edgeNodes: config.edgeNodes, connectedNodeMetadata: null, extensionsEnabled: false, connecting: false, @@ -686,15 +684,23 @@ const LogionChainContextProvider = (props: LogionChainContextProviderProps): JSX }); const accounts = state.allAccounts; + let logionClient: LogionClient; (async function() { - const rpcEndpoints = getEndpoints(); - const api = await buildApiClass(rpcEndpoints); + if (config.environment) { + logionClient = await newLogionClient(config.environment); + } else { + const rpcEndpoints = getEndpoints(); + logionClient = await LogionClient.create({ + rpcEndpoints, + directoryEndpoint: config.directory, + buildFileUploader: () => new BrowserAxiosFileUploader(), + logionClassificationLoc: UUID.fromAnyString(config.logionClassification), + creativeCommonsLoc: UUID.fromAnyString(config.creativeCommons), + }); + } + + const api = logionClient.logionApi; const peerId = await api.polkadot.rpc.system.localPeerId(); - const logionClient = await LogionClient.create({ - rpcEndpoints, - directoryEndpoint: config.directory, - buildFileUploader: () => new BrowserAxiosFileUploader(), - }); let startupTokens: AccountTokens; try { @@ -857,7 +863,7 @@ const LogionChainContextProvider = (props: LogionChainContextProviderProps): JSX type: 'SET_METAMASK_ACCOUNTS', metaMaskAccounts: [], }); - + const metaMaskEnabled = await enableMetaMask(config.APP_NAME); if(metaMaskEnabled) { const metaMaskAccounts = await allMetamaskAccounts(); From d7d8fae64612ff84bba342a97b84061dc7ff3015 Mon Sep 17 00:00:00 2001 From: Benoit Devos Date: Wed, 13 Mar 2024 16:01:23 +0100 Subject: [PATCH 2/2] feat: replace edgeNodes with rpcEndpoints; remove useless properties. logion-network/logion-internal#1099 --- public/config.js | 7 +------ src/config/development.json.sample | 17 ++++------------ src/config/entrypoint.test.tsx | 20 ------------------- src/config/index.tsx | 26 +++---------------------- src/config/production.json | 1 - src/config/test.json | 17 ++++------------ src/logion-chain/Connection.ts | 11 ----------- src/logion-chain/LogionChainContext.tsx | 5 ++--- 8 files changed, 14 insertions(+), 90 deletions(-) delete mode 100644 src/config/entrypoint.test.tsx diff --git a/public/config.js b/public/config.js index c0f091ca..1d64bd27 100644 --- a/public/config.js +++ b/public/config.js @@ -2,10 +2,5 @@ let CONFIG = { // Uncomment below lines to provide runtime configuration. // APP_NAME: "Logion Wallet", // directory: "http://localhost:8090/api", -// edgeNodes: [ -// { -// peerId: "12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2", -// socket: "ws://localhost:9944" -// } -// ] +// rpcEndpoints: [ "ws://localhost:9944" ] }; diff --git a/src/config/development.json.sample b/src/config/development.json.sample index 144d7e5d..74f06181 100644 --- a/src/config/development.json.sample +++ b/src/config/development.json.sample @@ -1,19 +1,10 @@ { "APP_NAME": "Logion Wallet DEV", "directory": "http://localhost:8090", - "edgeNodes": [ - { - "peerId": "12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2", - "socket": "ws://localhost:9944" - }, - { - "peerId": "12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust", - "socket": "ws://localhost:9945" - }, - { - "peerId": "12D3KooWJvyP3VJYymTqG7eH4PM5rN4T2agk5cdNCfNymAqwqcvZ", - "socket": "ws://localhost:9946" - } + "rpcEndpoints": [ + "ws://localhost:9944", + "ws://localhost:9945", + "ws://localhost:9946" ], "crossmintApiKey": "", "logionClassification": "", diff --git a/src/config/entrypoint.test.tsx b/src/config/entrypoint.test.tsx deleted file mode 100644 index c428b931..00000000 --- a/src/config/entrypoint.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import 'luxon'; - -const OLD_ENV = process.env; - -beforeEach(() => { - jest.resetModules() // Most important - it clears the cache - process.env = { ...OLD_ENV }; // Make a copy -}); - -afterAll(() => { - process.env = OLD_ENV; // Restore old environment -}); - -test("Config reads socket and keyring from env", () => { - process.env.REACT_APP_PROVIDER_SOCKET = "my-socket"; - process.env.REACT_APP_DEVELOPMENT_KEYRING = "true"; - const config = require('./index.tsx').default; - expect(config.PROVIDER_SOCKET).toBe("my-socket"); - expect(config.DEVELOPMENT_KEYRING).toBe("true"); -}); diff --git a/src/config/index.tsx b/src/config/index.tsx index 76e44066..585da260 100644 --- a/src/config/index.tsx +++ b/src/config/index.tsx @@ -1,17 +1,10 @@ import { EnvironmentString } from "@logion/client"; -export interface Node { - socket: string; - peerId: string; -} - export interface ConfigType { environment: EnvironmentString | undefined, APP_NAME: string, - DEVELOPMENT_KEYRING: boolean, - PROVIDER_SOCKET?: string, directory: string, - edgeNodes: Node[], + rpcEndpoints: string[], crossmintApiKey: string, logionClassification: string, creativeCommons: string, @@ -20,9 +13,8 @@ export interface ConfigType { export const DEFAULT_CONFIG: ConfigType = { environment: undefined, APP_NAME: "Logion Wallet", - DEVELOPMENT_KEYRING: true, directory: "", - edgeNodes: [], + rpcEndpoints: [], crossmintApiKey: "", logionClassification: "", creativeCommons: "", @@ -34,19 +26,7 @@ export interface EnvConfigType extends Record { const configEnv: EnvConfigType = require(`./${process.env.NODE_ENV}.json`); -const envVarNames: string[] = [ - 'REACT_APP_PROVIDER_SOCKET', - 'REACT_APP_DEVELOPMENT_KEYRING' -]; -const envVars: EnvConfigType = envVarNames.reduce((mem, n) => { - if (process.env[n] !== undefined) { - const configFieldName = n.slice(10); - mem[configFieldName] = process.env[n]; - } - return mem; -}, {}); - declare var CONFIG: any; -const config: ConfigType = { ...DEFAULT_CONFIG, ...configEnv, ...envVars, ...CONFIG }; +const config: ConfigType = { ...DEFAULT_CONFIG, ...configEnv, ...CONFIG }; export default config; diff --git a/src/config/production.json b/src/config/production.json index 1205499c..0d82e82c 100644 --- a/src/config/production.json +++ b/src/config/production.json @@ -1,6 +1,5 @@ { "APP_NAME": "Logion Wallet", "directory": "http://localhost:8090/api", - "edgeNodes": [], "crossmintApiKey": "" } diff --git a/src/config/test.json b/src/config/test.json index 13deef3d..f25ebe73 100644 --- a/src/config/test.json +++ b/src/config/test.json @@ -1,19 +1,10 @@ { "APP_NAME": "Logion Wallet TEST", "directory": "http://localhost:8090/api", - "edgeNodes": [ - { - "peerId": "12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2", - "socket": "ws://localhost:9944" - }, - { - "peerId": "12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust", - "socket": "ws://localhost:9945" - }, - { - "peerId": "12D3KooWJvyP3VJYymTqG7eH4PM5rN4T2agk5cdNCfNymAqwqcvZ", - "socket": "ws://localhost:9946" - } + "rpcEndpoints": [ + "ws://localhost:9944", + "ws://localhost:9945", + "ws://localhost:9946" ], "crossmintApiKey": "" } diff --git a/src/logion-chain/Connection.ts b/src/logion-chain/Connection.ts index c52db151..4f629593 100644 --- a/src/logion-chain/Connection.ts +++ b/src/logion-chain/Connection.ts @@ -1,14 +1,3 @@ -import config from '../config'; - export interface NodeMetadata { peerId: string } - -export function getEndpoints(): string[] { - const providerSocket = config.PROVIDER_SOCKET; - if(providerSocket !== undefined) { - return [ providerSocket ]; - } else { - return config.edgeNodes.map(node => node.socket); - } -} diff --git a/src/logion-chain/LogionChainContext.tsx b/src/logion-chain/LogionChainContext.tsx index edeafcaf..f94e4792 100644 --- a/src/logion-chain/LogionChainContext.tsx +++ b/src/logion-chain/LogionChainContext.tsx @@ -32,7 +32,7 @@ import { storeTokens } from '../common/Storage'; -import { getEndpoints, NodeMetadata } from './Connection'; +import { NodeMetadata } from './Connection'; import { BrowserAxiosFileUploader, newLogionClient } from '@logion/client-browser'; type ConsumptionStatus = 'PENDING' | 'STARTING' | 'STARTED'; @@ -689,9 +689,8 @@ const LogionChainContextProvider = (props: LogionChainContextProviderProps): JSX if (config.environment) { logionClient = await newLogionClient(config.environment); } else { - const rpcEndpoints = getEndpoints(); logionClient = await LogionClient.create({ - rpcEndpoints, + rpcEndpoints: config.rpcEndpoints, directoryEndpoint: config.directory, buildFileUploader: () => new BrowserAxiosFileUploader(), logionClassificationLoc: UUID.fromAnyString(config.logionClassification),