forked from Qortal/qortal
Merge branch 'master' into arbitrary-resources-cache
This commit is contained in:
commit
b4794ada72
@ -583,14 +583,15 @@ let res = await qortalRequest({
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Send foreign coin to address
|
### Send foreign coin to address
|
||||||
_Requires user approval_
|
_Requires user approval_<br />
|
||||||
|
Note: default fees can be found [here](https://github.com/Qortal/qortal-ui/blob/master/plugins/plugins/core/qdn/browser/browser.src.js#L205-L209).
|
||||||
```
|
```
|
||||||
let res = await qortalRequest({
|
let res = await qortalRequest({
|
||||||
action: "SEND_COIN",
|
action: "SEND_COIN",
|
||||||
coin: "LTC",
|
coin: "LTC",
|
||||||
destinationAddress: "LSdTvMHRm8sScqwCi6x9wzYQae8JeZhx6y",
|
destinationAddress: "LSdTvMHRm8sScqwCi6x9wzYQae8JeZhx6y",
|
||||||
amount: 1.00000000, // 1 LTC
|
amount: 1.00000000, // 1 LTC
|
||||||
fee: 0.00000020 // fee per byte
|
fee: 0.00000020 // Optional fee per byte (default fee used if omitted, recommended) - not used for QORT or ARRR
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>4.1.2</version>
|
<version>4.1.3</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
@ -96,7 +96,7 @@ public class ApiService {
|
|||||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||||
|
|
||||||
// BouncyCastle-specific SSLContext build
|
// BouncyCastle-specific SSLContext build
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "BCJSSE");
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||||
|
173
src/main/java/org/qortal/api/DevProxyService.java
Normal file
173
src/main/java/org/qortal/api/DevProxyService.java
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package org.qortal.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
|
||||||
|
import org.eclipse.jetty.server.*;
|
||||||
|
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.InetAccessHandler;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
|
import org.qortal.api.resource.AnnotationPostProcessor;
|
||||||
|
import org.qortal.api.resource.ApiDefinition;
|
||||||
|
import org.qortal.network.Network;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class DevProxyService {
|
||||||
|
|
||||||
|
private static DevProxyService instance;
|
||||||
|
|
||||||
|
private final ResourceConfig config;
|
||||||
|
private Server server;
|
||||||
|
|
||||||
|
private DevProxyService() {
|
||||||
|
this.config = new ResourceConfig();
|
||||||
|
this.config.packages("org.qortal.api.proxy.resource", "org.qortal.api.resource");
|
||||||
|
this.config.register(OpenApiResource.class);
|
||||||
|
this.config.register(ApiDefinition.class);
|
||||||
|
this.config.register(AnnotationPostProcessor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DevProxyService getInstance() {
|
||||||
|
if (instance == null)
|
||||||
|
instance = new DevProxyService();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Class<?>> getResources() {
|
||||||
|
return this.config.getClasses();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws DataException {
|
||||||
|
try {
|
||||||
|
// Create API server
|
||||||
|
|
||||||
|
// SSL support if requested
|
||||||
|
String keystorePathname = Settings.getInstance().getSslKeystorePathname();
|
||||||
|
String keystorePassword = Settings.getInstance().getSslKeystorePassword();
|
||||||
|
|
||||||
|
if (keystorePathname != null && keystorePassword != null) {
|
||||||
|
// SSL version
|
||||||
|
if (!Files.isReadable(Path.of(keystorePathname)))
|
||||||
|
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||||
|
|
||||||
|
// BouncyCastle-specific SSLContext build
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
||||||
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||||
|
|
||||||
|
try (InputStream keystoreStream = Files.newInputStream(Paths.get(keystorePathname))) {
|
||||||
|
keyStore.load(keystoreStream, keystorePassword.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
|
||||||
|
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
|
||||||
|
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
sslContextFactory.setSslContext(sslContext);
|
||||||
|
|
||||||
|
this.server = new Server();
|
||||||
|
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.setSecureScheme("https");
|
||||||
|
httpConfig.setSecurePort(Settings.getInstance().getDevProxyPort());
|
||||||
|
|
||||||
|
SecureRequestCustomizer src = new SecureRequestCustomizer();
|
||||||
|
httpConfig.addCustomizer(src);
|
||||||
|
|
||||||
|
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);
|
||||||
|
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
|
||||||
|
|
||||||
|
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
||||||
|
new DetectorConnectionFactory(sslConnectionFactory),
|
||||||
|
httpConnectionFactory);
|
||||||
|
portUnifiedConnector.setHost(Network.getInstance().getBindAddress());
|
||||||
|
portUnifiedConnector.setPort(Settings.getInstance().getDevProxyPort());
|
||||||
|
|
||||||
|
this.server.addConnector(portUnifiedConnector);
|
||||||
|
} else {
|
||||||
|
// Non-SSL
|
||||||
|
InetAddress bindAddr = InetAddress.getByName(Network.getInstance().getBindAddress());
|
||||||
|
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getDevProxyPort());
|
||||||
|
this.server = new Server(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handler
|
||||||
|
ErrorHandler errorHandler = new ApiErrorHandler();
|
||||||
|
this.server.setErrorHandler(errorHandler);
|
||||||
|
|
||||||
|
// Request logging
|
||||||
|
if (Settings.getInstance().isDevProxyLoggingEnabled()) {
|
||||||
|
RequestLogWriter logWriter = new RequestLogWriter("devproxy-requests.log");
|
||||||
|
logWriter.setAppend(true);
|
||||||
|
logWriter.setTimeZone("UTC");
|
||||||
|
RequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT);
|
||||||
|
this.server.setRequestLog(requestLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access handler (currently no whitelist is used)
|
||||||
|
InetAccessHandler accessHandler = new InetAccessHandler();
|
||||||
|
this.server.setHandler(accessHandler);
|
||||||
|
|
||||||
|
// URL rewriting
|
||||||
|
RewriteHandler rewriteHandler = new RewriteHandler();
|
||||||
|
accessHandler.setHandler(rewriteHandler);
|
||||||
|
|
||||||
|
// Context
|
||||||
|
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||||
|
context.setContextPath("/");
|
||||||
|
rewriteHandler.setHandler(context);
|
||||||
|
|
||||||
|
// Cross-origin resource sharing
|
||||||
|
FilterHolder corsFilterHolder = new FilterHolder(CrossOriginFilter.class);
|
||||||
|
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
|
||||||
|
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET, POST, DELETE");
|
||||||
|
corsFilterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
|
||||||
|
context.addFilter(corsFilterHolder, "/*", null);
|
||||||
|
|
||||||
|
// API servlet
|
||||||
|
ServletContainer container = new ServletContainer(this.config);
|
||||||
|
ServletHolder apiServlet = new ServletHolder(container);
|
||||||
|
apiServlet.setInitOrder(1);
|
||||||
|
context.addServlet(apiServlet, "/*");
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
this.server.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Failed to start
|
||||||
|
throw new DataException("Failed to start developer proxy", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
// Stop server
|
||||||
|
this.server.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Failed to stop
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server = null;
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -69,7 +69,7 @@ public class DomainMapService {
|
|||||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||||
|
|
||||||
// BouncyCastle-specific SSLContext build
|
// BouncyCastle-specific SSLContext build
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "BCJSSE");
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||||
|
@ -69,7 +69,7 @@ public class GatewayService {
|
|||||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||||
|
|
||||||
// BouncyCastle-specific SSLContext build
|
// BouncyCastle-specific SSLContext build
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "BCJSSE");
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||||
|
@ -24,11 +24,11 @@ public class HTMLParser {
|
|||||||
private String theme;
|
private String theme;
|
||||||
private boolean usingCustomRouting;
|
private boolean usingCustomRouting;
|
||||||
|
|
||||||
public HTMLParser(String resourceId, String inPath, String prefix, boolean usePrefix, byte[] data,
|
public HTMLParser(String resourceId, String inPath, String prefix, boolean includeResourceIdInPrefix, byte[] data,
|
||||||
String qdnContext, Service service, String identifier, String theme, boolean usingCustomRouting) {
|
String qdnContext, Service service, String identifier, String theme, boolean usingCustomRouting) {
|
||||||
String inPathWithoutFilename = inPath.contains("/") ? inPath.substring(0, inPath.lastIndexOf('/')) : "";
|
String inPathWithoutFilename = inPath.contains("/") ? inPath.substring(0, inPath.lastIndexOf('/')) : String.format("/%s",inPath);
|
||||||
this.qdnBase = usePrefix ? String.format("%s/%s", prefix, resourceId) : "";
|
this.qdnBase = includeResourceIdInPrefix ? String.format("%s/%s", prefix, resourceId) : prefix;
|
||||||
this.qdnBaseWithPath = usePrefix ? String.format("%s/%s%s", prefix, resourceId, inPathWithoutFilename) : "";
|
this.qdnBaseWithPath = includeResourceIdInPrefix ? String.format("%s/%s%s", prefix, resourceId, inPathWithoutFilename) : String.format("%s%s", prefix, inPathWithoutFilename);
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.qdnContext = qdnContext;
|
this.qdnContext = qdnContext;
|
||||||
this.resourceId = resourceId;
|
this.resourceId = resourceId;
|
||||||
@ -82,7 +82,7 @@ public class HTMLParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isHtmlFile(String path) {
|
public static boolean isHtmlFile(String path) {
|
||||||
if (path.endsWith(".html") || path.endsWith(".htm")) {
|
if (path.endsWith(".html") || path.endsWith(".htm") || path.equals("")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -48,10 +48,10 @@ public class DomainMapResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
||||||
String inPath, String secret58, String prefix, boolean usePrefix, boolean async) {
|
String inPath, String secret58, String prefix, boolean includeResourceIdInPrefix, boolean async) {
|
||||||
|
|
||||||
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
|
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
|
||||||
secret58, prefix, usePrefix, async, "domainMap", request, response, context);
|
secret58, prefix, includeResourceIdInPrefix, async, "domainMap", request, response, context);
|
||||||
return renderer.render();
|
return renderer.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public class GatewayResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private HttpServletResponse parsePath(String inPath, String qdnContext, String secret58, boolean usePrefix, boolean async) {
|
private HttpServletResponse parsePath(String inPath, String qdnContext, String secret58, boolean includeResourceIdInPrefix, boolean async) {
|
||||||
|
|
||||||
if (inPath == null || inPath.equals("")) {
|
if (inPath == null || inPath.equals("")) {
|
||||||
// Assume not a real file
|
// Assume not a real file
|
||||||
@ -143,7 +143,7 @@ public class GatewayResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(name, ResourceIdType.NAME, service, identifier, outPath,
|
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(name, ResourceIdType.NAME, service, identifier, outPath,
|
||||||
secret58, prefix, usePrefix, async, qdnContext, request, response, context);
|
secret58, prefix, includeResourceIdInPrefix, async, qdnContext, request, response, context);
|
||||||
return renderer.render();
|
return renderer.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
package org.qortal.api.proxy.resource;
|
||||||
|
|
||||||
|
import org.qortal.api.ApiError;
|
||||||
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
|
import org.qortal.api.HTMLParser;
|
||||||
|
import org.qortal.arbitrary.misc.Service;
|
||||||
|
import org.qortal.controller.DevProxyManager;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
|
||||||
|
@Path("/")
|
||||||
|
public class DevProxyServerResource {
|
||||||
|
|
||||||
|
@Context HttpServletRequest request;
|
||||||
|
@Context HttpServletResponse response;
|
||||||
|
@Context ServletContext context;
|
||||||
|
|
||||||
|
|
||||||
|
@GET
|
||||||
|
public HttpServletResponse getProxyIndex() {
|
||||||
|
return this.proxy("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{path:.*}")
|
||||||
|
public HttpServletResponse getProxyPath(@PathParam("path") String inPath) {
|
||||||
|
return this.proxy(inPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletResponse proxy(String inPath) {
|
||||||
|
try {
|
||||||
|
String source = DevProxyManager.getInstance().getSourceHostAndPort();
|
||||||
|
|
||||||
|
// Convert localhost / 127.0.0.1 to IPv6 [::1]
|
||||||
|
if (source.startsWith("localhost") || source.startsWith("127.0.0.1")) {
|
||||||
|
int port = 80;
|
||||||
|
String[] parts = source.split(":");
|
||||||
|
if (parts.length > 1) {
|
||||||
|
port = Integer.parseInt(parts[1]);
|
||||||
|
}
|
||||||
|
source = String.format("[::1]:%d", port);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inPath.startsWith("/")) {
|
||||||
|
inPath = "/" + inPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
String queryString = request.getQueryString() != null ? "?" + request.getQueryString() : "";
|
||||||
|
|
||||||
|
// Open URL
|
||||||
|
String urlString = String.format("http://%s%s%s", source, inPath, queryString);
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||||
|
con.setRequestMethod(request.getMethod());
|
||||||
|
|
||||||
|
// Proxy the request headers
|
||||||
|
Enumeration<String> headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String headerName = headerNames.nextElement();
|
||||||
|
String headerValue = request.getHeader(headerName);
|
||||||
|
con.setRequestProperty(headerName, headerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: proxy any POST parameters from "request" to "con"
|
||||||
|
|
||||||
|
// Proxy the response code
|
||||||
|
int responseCode = con.getResponseCode();
|
||||||
|
response.setStatus(responseCode);
|
||||||
|
|
||||||
|
// Proxy the response headers
|
||||||
|
for (int i = 0; ; i++) {
|
||||||
|
String headerKey = con.getHeaderFieldKey(i);
|
||||||
|
String headerValue = con.getHeaderField(i);
|
||||||
|
if (headerKey != null && headerValue != null) {
|
||||||
|
response.addHeader(headerKey, headerValue);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
|
InputStream inputStream = con.getInputStream();
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
byte[] data = outputStream.toByteArray(); // TODO: limit file size that can be read into memory
|
||||||
|
|
||||||
|
// Close the streams
|
||||||
|
outputStream.close();
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
// Extract filename
|
||||||
|
String filename = "";
|
||||||
|
if (inPath.contains("/")) {
|
||||||
|
String[] parts = inPath.split("/");
|
||||||
|
if (parts.length > 0) {
|
||||||
|
filename = parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and modify output if needed
|
||||||
|
if (HTMLParser.isHtmlFile(filename)) {
|
||||||
|
// HTML file - needs to be parsed
|
||||||
|
HTMLParser htmlParser = new HTMLParser("", inPath, "", false, data, "proxy", Service.APP, null, "light", true);
|
||||||
|
htmlParser.addAdditionalHeaderTags();
|
||||||
|
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' data: blob:; img-src 'self' data: blob:; connect-src 'self' ws:; font-src 'self' data:;");
|
||||||
|
response.setContentType(con.getContentType());
|
||||||
|
response.setContentLength(htmlParser.getData().length);
|
||||||
|
response.getOutputStream().write(htmlParser.getData());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Regular file - can be streamed directly
|
||||||
|
response.addHeader("Content-Security-Policy", "default-src 'self'");
|
||||||
|
response.setContentType(con.getContentType());
|
||||||
|
response.setContentLength(data.length);
|
||||||
|
response.getOutputStream().write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
96
src/main/java/org/qortal/api/resource/DeveloperResource.java
Normal file
96
src/main/java/org/qortal/api/resource/DeveloperResource.java
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package org.qortal.api.resource;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.qortal.api.ApiError;
|
||||||
|
import org.qortal.api.ApiErrors;
|
||||||
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
|
import org.qortal.controller.DevProxyManager;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
|
||||||
|
@Path("/developer")
|
||||||
|
@Tag(name = "Developer Tools")
|
||||||
|
public class DeveloperResource {
|
||||||
|
|
||||||
|
@Context HttpServletRequest request;
|
||||||
|
@Context HttpServletResponse response;
|
||||||
|
@Context ServletContext context;
|
||||||
|
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/proxy/start")
|
||||||
|
@Operation(
|
||||||
|
summary = "Start proxy server, for real time QDN app/website development",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
description = "Host and port of source webserver to be proxied",
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string",
|
||||||
|
example = "127.0.0.1:5173"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "Port number of running server",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "number"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_CRITERIA})
|
||||||
|
public Integer startProxy(String sourceHostAndPort) {
|
||||||
|
// TODO: API key
|
||||||
|
DevProxyManager devProxyManager = DevProxyManager.getInstance();
|
||||||
|
try {
|
||||||
|
devProxyManager.setSourceHostAndPort(sourceHostAndPort);
|
||||||
|
devProxyManager.start();
|
||||||
|
return devProxyManager.getPort();
|
||||||
|
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/proxy/stop")
|
||||||
|
@Operation(
|
||||||
|
summary = "Stop proxy server",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if stopped",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "boolean"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public boolean stopProxy() {
|
||||||
|
DevProxyManager devProxyManager = DevProxyManager.getInstance();
|
||||||
|
devProxyManager.stop();
|
||||||
|
return !devProxyManager.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -157,10 +157,10 @@ public class RenderResource {
|
|||||||
|
|
||||||
|
|
||||||
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
||||||
String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String theme) {
|
String inPath, String secret58, String prefix, boolean includeResourceIdInPrefix, boolean async, String theme) {
|
||||||
|
|
||||||
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
|
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
|
||||||
secret58, prefix, usePrefix, async, "render", request, response, context);
|
secret58, prefix, includeResourceIdInPrefix, async, "render", request, response, context);
|
||||||
|
|
||||||
if (theme != null) {
|
if (theme != null) {
|
||||||
renderer.setTheme(theme);
|
renderer.setTheme(theme);
|
||||||
|
@ -40,7 +40,7 @@ public class ArbitraryDataRenderer {
|
|||||||
private String inPath;
|
private String inPath;
|
||||||
private final String secret58;
|
private final String secret58;
|
||||||
private final String prefix;
|
private final String prefix;
|
||||||
private final boolean usePrefix;
|
private final boolean includeResourceIdInPrefix;
|
||||||
private final boolean async;
|
private final boolean async;
|
||||||
private final String qdnContext;
|
private final String qdnContext;
|
||||||
private final HttpServletRequest request;
|
private final HttpServletRequest request;
|
||||||
@ -48,7 +48,7 @@ public class ArbitraryDataRenderer {
|
|||||||
private final ServletContext context;
|
private final ServletContext context;
|
||||||
|
|
||||||
public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
||||||
String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String qdnContext,
|
String inPath, String secret58, String prefix, boolean includeResourceIdInPrefix, boolean async, String qdnContext,
|
||||||
HttpServletRequest request, HttpServletResponse response, ServletContext context) {
|
HttpServletRequest request, HttpServletResponse response, ServletContext context) {
|
||||||
|
|
||||||
this.resourceId = resourceId;
|
this.resourceId = resourceId;
|
||||||
@ -58,7 +58,7 @@ public class ArbitraryDataRenderer {
|
|||||||
this.inPath = inPath;
|
this.inPath = inPath;
|
||||||
this.secret58 = secret58;
|
this.secret58 = secret58;
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
this.usePrefix = usePrefix;
|
this.includeResourceIdInPrefix = includeResourceIdInPrefix;
|
||||||
this.async = async;
|
this.async = async;
|
||||||
this.qdnContext = qdnContext;
|
this.qdnContext = qdnContext;
|
||||||
this.request = request;
|
this.request = request;
|
||||||
@ -159,7 +159,7 @@ public class ArbitraryDataRenderer {
|
|||||||
if (HTMLParser.isHtmlFile(filename)) {
|
if (HTMLParser.isHtmlFile(filename)) {
|
||||||
// HTML file - needs to be parsed
|
// HTML file - needs to be parsed
|
||||||
byte[] data = Files.readAllBytes(filePath); // TODO: limit file size that can be read into memory
|
byte[] data = Files.readAllBytes(filePath); // TODO: limit file size that can be read into memory
|
||||||
HTMLParser htmlParser = new HTMLParser(resourceId, inPath, prefix, usePrefix, data, qdnContext, service, identifier, theme, usingCustomRouting);
|
HTMLParser htmlParser = new HTMLParser(resourceId, inPath, prefix, includeResourceIdInPrefix, data, qdnContext, service, identifier, theme, usingCustomRouting);
|
||||||
htmlParser.addAdditionalHeaderTags();
|
htmlParser.addAdditionalHeaderTags();
|
||||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' data: blob:; img-src 'self' data: blob:;");
|
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' data: blob:; img-src 'self' data: blob:;");
|
||||||
response.setContentType(context.getMimeType(filename));
|
response.setContentType(context.getMimeType(filename));
|
||||||
|
74
src/main/java/org/qortal/controller/DevProxyManager.java
Normal file
74
src/main/java/org/qortal/controller/DevProxyManager.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package org.qortal.controller;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qortal.api.DevProxyService;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
|
|
||||||
|
public class DevProxyManager {
|
||||||
|
|
||||||
|
protected static final Logger LOGGER = LogManager.getLogger(DevProxyManager.class);
|
||||||
|
|
||||||
|
private static DevProxyManager instance;
|
||||||
|
|
||||||
|
private boolean running = false;
|
||||||
|
|
||||||
|
private String sourceHostAndPort = "127.0.0.1:5173"; // Default for React/Vite
|
||||||
|
|
||||||
|
private DevProxyManager() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DevProxyManager getInstance() {
|
||||||
|
if (instance == null)
|
||||||
|
instance = new DevProxyManager();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws DataException {
|
||||||
|
synchronized(this) {
|
||||||
|
if (this.running) {
|
||||||
|
// Already running
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Starting developer proxy service on port %d", Settings.getInstance().getDevProxyPort()));
|
||||||
|
DevProxyService devProxyService = DevProxyService.getInstance();
|
||||||
|
devProxyService.start();
|
||||||
|
this.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
synchronized(this) {
|
||||||
|
if (!this.running) {
|
||||||
|
// Not running
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Shutting down developer proxy service"));
|
||||||
|
DevProxyService devProxyService = DevProxyService.getInstance();
|
||||||
|
devProxyService.stop();
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceHostAndPort(String sourceHostAndPort) {
|
||||||
|
this.sourceHostAndPort = sourceHostAndPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceHostAndPort() {
|
||||||
|
return this.sourceHostAndPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPort() {
|
||||||
|
return Settings.getInstance().getDevProxyPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return this.running;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,7 +28,7 @@ public abstract class TrustlessSSLSocketFactory {
|
|||||||
private static final SSLContext sc;
|
private static final SSLContext sc;
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
sc = SSLContext.getInstance("SSL");
|
sc = SSLContext.getInstance("TLSv1.3");
|
||||||
sc.init(null, TRUSTLESS_MANAGER, new java.security.SecureRandom());
|
sc.init(null, TRUSTLESS_MANAGER, new java.security.SecureRandom());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -265,7 +265,7 @@ public enum Handshake {
|
|||||||
private static final long PEER_VERSION_131 = 0x0100030001L;
|
private static final long PEER_VERSION_131 = 0x0100030001L;
|
||||||
|
|
||||||
/** Minimum peer version that we are allowed to communicate with */
|
/** Minimum peer version that we are allowed to communicate with */
|
||||||
private static final String MIN_PEER_VERSION = "4.0.0";
|
private static final String MIN_PEER_VERSION = "4.1.1";
|
||||||
|
|
||||||
private static final int POW_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes
|
private static final int POW_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes
|
||||||
private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits
|
private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits
|
||||||
|
@ -47,6 +47,9 @@ public class Settings {
|
|||||||
private static final int MAINNET_GATEWAY_PORT = 80;
|
private static final int MAINNET_GATEWAY_PORT = 80;
|
||||||
private static final int TESTNET_GATEWAY_PORT = 8080;
|
private static final int TESTNET_GATEWAY_PORT = 8080;
|
||||||
|
|
||||||
|
private static final int MAINNET_DEV_PROXY_PORT = 12393;
|
||||||
|
private static final int TESTNET_DEV_PROXY_PORT = 62393;
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
||||||
private static final String SETTINGS_FILENAME = "settings.json";
|
private static final String SETTINGS_FILENAME = "settings.json";
|
||||||
|
|
||||||
@ -107,6 +110,11 @@ public class Settings {
|
|||||||
private boolean gatewayLoggingEnabled = false;
|
private boolean gatewayLoggingEnabled = false;
|
||||||
private boolean gatewayLoopbackEnabled = false;
|
private boolean gatewayLoopbackEnabled = false;
|
||||||
|
|
||||||
|
// Developer Proxy
|
||||||
|
private Integer devProxyPort;
|
||||||
|
private boolean devProxyLoggingEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
// Specific to this node
|
// Specific to this node
|
||||||
private boolean wipeUnconfirmedOnStart = false;
|
private boolean wipeUnconfirmedOnStart = false;
|
||||||
/** Maximum number of unconfirmed transactions allowed per account */
|
/** Maximum number of unconfirmed transactions allowed per account */
|
||||||
@ -219,7 +227,7 @@ public class Settings {
|
|||||||
public long recoveryModeTimeout = 24 * 60 * 60 * 1000L;
|
public long recoveryModeTimeout = 24 * 60 * 60 * 1000L;
|
||||||
|
|
||||||
/** Minimum peer version number required in order to sync with them */
|
/** Minimum peer version number required in order to sync with them */
|
||||||
private String minPeerVersion = "4.1.1";
|
private String minPeerVersion = "4.1.2";
|
||||||
/** Whether to allow connections with peers below minPeerVersion
|
/** Whether to allow connections with peers below minPeerVersion
|
||||||
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
||||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||||
@ -649,6 +657,18 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getDevProxyPort() {
|
||||||
|
if (this.devProxyPort != null)
|
||||||
|
return this.devProxyPort;
|
||||||
|
|
||||||
|
return this.isTestNet ? TESTNET_DEV_PROXY_PORT : MAINNET_DEV_PROXY_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDevProxyLoggingEnabled() {
|
||||||
|
return this.devProxyLoggingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean getWipeUnconfirmedOnStart() {
|
public boolean getWipeUnconfirmedOnStart() {
|
||||||
return this.wipeUnconfirmedOnStart;
|
return this.wipeUnconfirmedOnStart;
|
||||||
}
|
}
|
||||||
|
83
src/main/resources/i18n/ApiError_jp.properties
Normal file
83
src/main/resources/i18n/ApiError_jp.properties
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||||
|
# Keys are from api.ApiError enum
|
||||||
|
|
||||||
|
# "localeLang": "jp",
|
||||||
|
|
||||||
|
### Common ###
|
||||||
|
JSON = JSON メッセージの解析に失敗しました
|
||||||
|
|
||||||
|
INSUFFICIENT_BALANCE = 残高不足
|
||||||
|
|
||||||
|
UNAUTHORIZED = APIコール未承認
|
||||||
|
|
||||||
|
REPOSITORY_ISSUE = リポジトリエラー
|
||||||
|
|
||||||
|
NON_PRODUCTION = この APIコールはプロダクションシステムでは許可されていません
|
||||||
|
|
||||||
|
BLOCKCHAIN_NEEDS_SYNC = ブロックチェーンをまず同期する必要があります
|
||||||
|
|
||||||
|
NO_TIME_SYNC = 時刻が未同期
|
||||||
|
|
||||||
|
### Validation ###
|
||||||
|
INVALID_SIGNATURE = 無効な署名
|
||||||
|
|
||||||
|
INVALID_ADDRESS = 無効なアドレス
|
||||||
|
|
||||||
|
INVALID_PUBLIC_KEY = 無効な公開鍵
|
||||||
|
|
||||||
|
INVALID_DATA = 無効なデータ
|
||||||
|
|
||||||
|
INVALID_NETWORK_ADDRESS = 無効なネットワーク アドレス
|
||||||
|
|
||||||
|
ADDRESS_UNKNOWN = 不明なアカウントアドレス
|
||||||
|
|
||||||
|
INVALID_CRITERIA = 無効な検索条件
|
||||||
|
|
||||||
|
INVALID_REFERENCE = 無効な参照
|
||||||
|
|
||||||
|
TRANSFORMATION_ERROR = JSONをトランザクションに変換出来ませんでした
|
||||||
|
|
||||||
|
INVALID_PRIVATE_KEY = 無効な秘密鍵
|
||||||
|
|
||||||
|
INVALID_HEIGHT = 無効なブロック高
|
||||||
|
|
||||||
|
CANNOT_MINT = アカウントはミント出来ません
|
||||||
|
|
||||||
|
### Blocks ###
|
||||||
|
BLOCK_UNKNOWN = 不明なブロック
|
||||||
|
|
||||||
|
### Transactions ###
|
||||||
|
TRANSACTION_UNKNOWN = 不明なトランザクション
|
||||||
|
|
||||||
|
PUBLIC_KEY_NOT_FOUND = 公開鍵が見つかりません
|
||||||
|
|
||||||
|
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||||
|
TRANSACTION_INVALID = 無効なトランザクション: %s (%s)
|
||||||
|
|
||||||
|
### Naming ###
|
||||||
|
NAME_UNKNOWN = 不明な名前
|
||||||
|
|
||||||
|
### Asset ###
|
||||||
|
INVALID_ASSET_ID = 無効なアセット ID
|
||||||
|
|
||||||
|
INVALID_ORDER_ID = 無効なアセット注文 ID
|
||||||
|
|
||||||
|
ORDER_UNKNOWN = 不明なアセット注文 ID
|
||||||
|
|
||||||
|
### Groups ###
|
||||||
|
GROUP_UNKNOWN = 不明なグループ
|
||||||
|
|
||||||
|
### Foreign Blockchain ###
|
||||||
|
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = 外部ブロックチェーンまたはElectrumXネットワークの問題
|
||||||
|
|
||||||
|
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = 外部ブロックチェーンの残高が不足しています
|
||||||
|
|
||||||
|
FOREIGN_BLOCKCHAIN_TOO_SOON = 外部ブロックチェーン トランザクションのブロードキャストが時期尚早 (ロックタイム/ブロック時間の中央値)
|
||||||
|
|
||||||
|
### Trade Portal ###
|
||||||
|
ORDER_SIZE_TOO_SMALL = 注文金額が低すぎます
|
||||||
|
|
||||||
|
### Data ###
|
||||||
|
FILE_NOT_FOUND = ファイルが見つかりません
|
||||||
|
|
||||||
|
NO_REPLY = ピアが制限時間内に応答しませんでした
|
48
src/main/resources/i18n/SysTray_jp.properties
Normal file
48
src/main/resources/i18n/SysTray_jp.properties
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||||
|
# SysTray pop-up menu # Japanese translation by R M 2023
|
||||||
|
|
||||||
|
APPLYING_UPDATE_AND_RESTARTING = 自動更新を適用して再起動しています...
|
||||||
|
|
||||||
|
AUTO_UPDATE = 自動更新
|
||||||
|
|
||||||
|
BLOCK_HEIGHT = ブロック高
|
||||||
|
|
||||||
|
BLOCKS_REMAINING = 残りのブロック
|
||||||
|
|
||||||
|
BUILD_VERSION = ビルドバージョン
|
||||||
|
|
||||||
|
CHECK_TIME_ACCURACY = 時刻の精度を確認
|
||||||
|
|
||||||
|
CONNECTING = 接続中
|
||||||
|
|
||||||
|
CONNECTION = 接続
|
||||||
|
|
||||||
|
CONNECTIONS = 接続
|
||||||
|
|
||||||
|
CREATING_BACKUP_OF_DB_FILES = データベース ファイルのバックアップを作成中...
|
||||||
|
|
||||||
|
DB_BACKUP = データベースのバックアップ
|
||||||
|
|
||||||
|
DB_CHECKPOINT = データベースのチェックポイント
|
||||||
|
|
||||||
|
DB_MAINTENANCE = データベースのメンテナンス
|
||||||
|
|
||||||
|
EXIT = 終了
|
||||||
|
|
||||||
|
LITE_NODE = ライトノード
|
||||||
|
|
||||||
|
MINTING_DISABLED = ミント一時中止中
|
||||||
|
|
||||||
|
MINTING_ENABLED = \u2714 ミント
|
||||||
|
|
||||||
|
OPEN_UI = UIを開く
|
||||||
|
|
||||||
|
PERFORMING_DB_CHECKPOINT = コミットされていないデータベースの変更を保存中...
|
||||||
|
|
||||||
|
PERFORMING_DB_MAINTENANCE = 定期メンテナンスを実行中...
|
||||||
|
|
||||||
|
SYNCHRONIZE_CLOCK = 時刻を同期
|
||||||
|
|
||||||
|
SYNCHRONIZING_BLOCKCHAIN = ブロックチェーンを同期中
|
||||||
|
|
||||||
|
SYNCHRONIZING_CLOCK = 時刻を同期中
|
195
src/main/resources/i18n/TransactionValidity_jp.properties
Normal file
195
src/main/resources/i18n/TransactionValidity_jp.properties
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|
ACCOUNT_ALREADY_EXISTS = 既にアカウントは存在します
|
||||||
|
|
||||||
|
ACCOUNT_CANNOT_REWARD_SHARE = アカウントは報酬シェアが出来ません
|
||||||
|
|
||||||
|
ADDRESS_ABOVE_RATE_LIMIT = アドレスが指定されたレート制限に達しました
|
||||||
|
|
||||||
|
ADDRESS_BLOCKED = このアドレスはブロックされています
|
||||||
|
|
||||||
|
ALREADY_GROUP_ADMIN = 既ににグループ管理者です
|
||||||
|
|
||||||
|
ALREADY_GROUP_MEMBER = 既にグループメンバーです
|
||||||
|
|
||||||
|
ALREADY_VOTED_FOR_THAT_OPTION = 既にそのオプションに投票しています
|
||||||
|
|
||||||
|
ASSET_ALREADY_EXISTS = 既にアセットは存在します
|
||||||
|
|
||||||
|
ASSET_DOES_NOT_EXIST = アセットが存在しません
|
||||||
|
|
||||||
|
ASSET_DOES_NOT_MATCH_AT = アセットがATのアセットと一致しません
|
||||||
|
|
||||||
|
ASSET_NOT_SPENDABLE = 資産が使用不可です
|
||||||
|
|
||||||
|
AT_ALREADY_EXISTS = 既にATが存在します
|
||||||
|
|
||||||
|
AT_IS_FINISHED = ATが終了しました
|
||||||
|
|
||||||
|
AT_UNKNOWN = 不明なAT
|
||||||
|
|
||||||
|
BAN_EXISTS = 既にバンされてます
|
||||||
|
|
||||||
|
BAN_UNKNOWN = 不明なバン
|
||||||
|
|
||||||
|
BANNED_FROM_GROUP = グループからのバンされています
|
||||||
|
|
||||||
|
BUYER_ALREADY_OWNER = 既に購入者が所有者です
|
||||||
|
|
||||||
|
CLOCK_NOT_SYNCED = 時刻が未同期
|
||||||
|
|
||||||
|
DUPLICATE_MESSAGE = このアドレスは重複メッセージを送信しました
|
||||||
|
|
||||||
|
DUPLICATE_OPTION = 重複したオプション
|
||||||
|
|
||||||
|
GROUP_ALREADY_EXISTS = 既にグループは存在します
|
||||||
|
|
||||||
|
GROUP_APPROVAL_DECIDED = 既にグループの承認は決定されています
|
||||||
|
|
||||||
|
GROUP_APPROVAL_NOT_REQUIRED = グループ承認が不必要
|
||||||
|
|
||||||
|
GROUP_DOES_NOT_EXIST = グループが存在しません
|
||||||
|
|
||||||
|
GROUP_ID_MISMATCH = グループ ID が不一致
|
||||||
|
|
||||||
|
GROUP_OWNER_CANNOT_LEAVE = グループ所有者はグループを退会出来ません
|
||||||
|
|
||||||
|
HAVE_EQUALS_WANT = 持っている資産は欲しい資産と同じです
|
||||||
|
|
||||||
|
INCORRECT_NONCE = 不正な PoW ナンス
|
||||||
|
|
||||||
|
INSUFFICIENT_FEE = 手数料が不十分です
|
||||||
|
|
||||||
|
INVALID_ADDRESS = 無効なアドレス
|
||||||
|
|
||||||
|
INVALID_AMOUNT = 無効な金額
|
||||||
|
|
||||||
|
INVALID_ASSET_OWNER = 無効なアセット所有者
|
||||||
|
|
||||||
|
INVALID_AT_TRANSACTION = 無効なATトランザクション
|
||||||
|
|
||||||
|
INVALID_AT_TYPE_LENGTH = 無効なATの「タイプ」の長さです
|
||||||
|
|
||||||
|
INVALID_BUT_OK = 無効だがOK
|
||||||
|
|
||||||
|
INVALID_CREATION_BYTES = 無効な作成バイト数
|
||||||
|
|
||||||
|
INVALID_DATA_LENGTH = 無効なデータ長
|
||||||
|
|
||||||
|
INVALID_DESCRIPTION_LENGTH = 無効な概要の長さ
|
||||||
|
|
||||||
|
INVALID_GROUP_APPROVAL_THRESHOLD = 無効なグループ承認のしきい値
|
||||||
|
|
||||||
|
INVALID_GROUP_BLOCK_DELAY = 無効なグループ承認のブロック遅延
|
||||||
|
|
||||||
|
INVALID_GROUP_ID = 無効なグループ ID
|
||||||
|
|
||||||
|
INVALID_GROUP_OWNER = 無効なグループ所有者
|
||||||
|
|
||||||
|
INVALID_LIFETIME = 無効な有効期間
|
||||||
|
|
||||||
|
INVALID_NAME_LENGTH = 無効な名前の長さです
|
||||||
|
|
||||||
|
INVALID_NAME_OWNER = 無効な名前の所有者
|
||||||
|
|
||||||
|
INVALID_OPTION_LENGTH = 無効なオプションの長さ
|
||||||
|
|
||||||
|
INVALID_OPTIONS_COUNT = 無効なオプションの数
|
||||||
|
|
||||||
|
INVALID_ORDER_CREATOR = 無効な注文作成者
|
||||||
|
|
||||||
|
INVALID_PAYMENTS_COUNT = 無効な入出金数
|
||||||
|
|
||||||
|
INVALID_PUBLIC_KEY = 無効な公開鍵
|
||||||
|
|
||||||
|
INVALID_QUANTITY = 無効な数量
|
||||||
|
|
||||||
|
INVALID_REFERENCE = 無効な参照
|
||||||
|
|
||||||
|
INVALID_RETURN = 無効な返品
|
||||||
|
|
||||||
|
INVALID_REWARD_SHARE_PERCENT = 無効な報酬シェア率
|
||||||
|
|
||||||
|
INVALID_SELLER = 無効な販売者
|
||||||
|
|
||||||
|
INVALID_TAGS_LENGTH = 無効な「タグ」の長さ
|
||||||
|
|
||||||
|
INVALID_TIMESTAMP_SIGNATURE = 無効なタイムスタンプ署名
|
||||||
|
|
||||||
|
INVALID_TX_GROUP_ID = 無効なトランザクション グループ ID
|
||||||
|
|
||||||
|
INVALID_VALUE_LENGTH = 無効な「値」の長さ
|
||||||
|
|
||||||
|
INVITE_UNKNOWN = 不明なグループ招待
|
||||||
|
|
||||||
|
JOIN_REQUEST_EXISTS = 既にグループ参加リクエストが存在します
|
||||||
|
|
||||||
|
MAXIMUM_REWARD_SHARES = 既にこのアカウントの報酬シェアは最大です
|
||||||
|
|
||||||
|
MISSING_CREATOR = 作成者が見つかりません
|
||||||
|
|
||||||
|
MULTIPLE_NAMES_FORBIDDEN = アカウントごとに複数の登録名は禁止されています
|
||||||
|
|
||||||
|
NAME_ALREADY_FOR_SALE = 既に名前は販売中です
|
||||||
|
|
||||||
|
NAME_ALREADY_REGISTERED = 既に名前は登録されています
|
||||||
|
|
||||||
|
NAME_BLOCKED = この名前はブロックされています
|
||||||
|
|
||||||
|
NAME_DOES_NOT_EXIST = 名前は存在しません
|
||||||
|
|
||||||
|
NAME_NOT_FOR_SALE = 名前は非売品です
|
||||||
|
|
||||||
|
NAME_NOT_NORMALIZED = 名前は Unicode の「正規化」形式ではありません
|
||||||
|
|
||||||
|
NEGATIVE_AMOUNT = 無効な/負の金額
|
||||||
|
|
||||||
|
NEGATIVE_FEE = 無効な/負の料金
|
||||||
|
|
||||||
|
NEGATIVE_PRICE = 無効な/負の価格
|
||||||
|
|
||||||
|
NO_BALANCE = 残高が不足しています
|
||||||
|
|
||||||
|
NO_BLOCKCHAIN_LOCK = ノードのブロックチェーンは現在ビジーです
|
||||||
|
|
||||||
|
NO_FLAG_PERMISSION = アカウントにはその権限がありません
|
||||||
|
|
||||||
|
NOT_GROUP_ADMIN = アカウントはグループ管理者ではありません
|
||||||
|
|
||||||
|
NOT_GROUP_MEMBER = アカウントはグループメンバーではありません
|
||||||
|
|
||||||
|
NOT_MINTING_ACCOUNT = アカウントはミント出来ません
|
||||||
|
|
||||||
|
NOT_YET_RELEASED = 機能はまだリリースされていません
|
||||||
|
|
||||||
|
OK = OK
|
||||||
|
|
||||||
|
ORDER_ALREADY_CLOSED = 既に資産取引注文は終了しています
|
||||||
|
|
||||||
|
ORDER_DOES_NOT_EXIST = 資産取引注文が存在しません
|
||||||
|
|
||||||
|
POLL_ALREADY_EXISTS = 既に投票は存在します
|
||||||
|
|
||||||
|
POLL_DOES_NOT_EXIST = 投票は存在しません
|
||||||
|
|
||||||
|
POLL_OPTION_DOES_NOT_EXIST = 投票オプションが存在しません
|
||||||
|
|
||||||
|
PUBLIC_KEY_UNKNOWN = 不明な公開鍵
|
||||||
|
|
||||||
|
REWARD_SHARE_UNKNOWN = 不明な報酬シェア
|
||||||
|
|
||||||
|
SELF_SHARE_EXISTS = 既に自己シェア(報酬シェア)が存在します
|
||||||
|
|
||||||
|
TIMESTAMP_TOO_NEW = タイムスタンプが新しすぎます
|
||||||
|
|
||||||
|
TIMESTAMP_TOO_OLD = タイムスタンプが古すぎます
|
||||||
|
|
||||||
|
TOO_MANY_UNCONFIRMED = アカウントに保留中の未承認トランザクションが多すぎます
|
||||||
|
|
||||||
|
TRANSACTION_ALREADY_CONFIRMED = 既にトランザクションは承認されています
|
||||||
|
|
||||||
|
TRANSACTION_ALREADY_EXISTS = 既にトランザクションは存在します
|
||||||
|
|
||||||
|
TRANSACTION_UNKNOWN = 不明なトランザクション
|
||||||
|
|
||||||
|
TX_GROUP_ID_MISMATCH = トランザクションのグループIDが一致しません
|
@ -472,6 +472,10 @@ function getDefaultTimeout(action) {
|
|||||||
// Allow extra time for other actions that create transactions, even if there is no PoW
|
// Allow extra time for other actions that create transactions, even if there is no PoW
|
||||||
return 5 * 60 * 1000;
|
return 5 * 60 * 1000;
|
||||||
|
|
||||||
|
case "GET_WALLET_BALANCE":
|
||||||
|
// Getting a wallet balance can take a while, if there are many transactions
|
||||||
|
return 2 * 60 * 1000;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user