moeralib (GitHub) is a library that allows Java applications to easily
interact with the Moera network.
The library is available from Maven Central. For a JVM application, add both
org.moera:moeralib (the core library) and org.moera:moeralib-jvm (the OkHttp
HTTP and JSON-RPC transports).
The core library and the transport bindings are versioned independently. Replace
MOERALIB_VERSION and JVM_VERSION below with compatible released versions from
Maven Central; they do not have to be the same. Declare both dependencies
explicitly: moeralib-jvm declares the core library with Maven scope provided,
so it is not included transitively in your application.
Add both dependencies to the <dependencies> section of your pom.xml:
<dependencies>
<dependency>
<groupId>org.moera</groupId>
<artifactId>moeralib</artifactId>
<version>MOERALIB_VERSION</version>
</dependency>
<dependency>
<groupId>org.moera</groupId>
<artifactId>moeralib-jvm</artifactId>
<version>JVM_VERSION</version>
</dependency>
</dependencies>
For a Java project using the Groovy DSL (build.gradle):
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.moera:moeralib:MOERALIB_VERSION'
implementation 'org.moera:moeralib-jvm:JVM_VERSION'
}
Or using the Kotlin DSL (build.gradle.kts):
plugins {
java
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.moera:moeralib:MOERALIB_VERSION")
implementation("org.moera:moeralib-jvm:JVM_VERSION")
}
If your project already declares plugins, repositories, or dependencies, merge the
corresponding entries into the existing blocks. If repositories are configured
centrally in settings.gradle or settings.gradle.kts, add mavenCentral() there.
See the Gradle documentation on declaring dependencies and repositories.
For Android applications, replace org.moera:moeralib-jvm with
org.moera:moeralib-android and select a compatible version of those bindings.
Keep the explicit org.moera:moeralib dependency. Include only one transport
artifact: the JVM and Android artifacts provide the same transport class names.
The examples below use these transports explicitly.
org.moera.lib.naming
package contains classes for interacting with Moera naming service. To access a naming server,
create a MoeraNaming instance with an OkHttpJsonRpcFetcher configured with the
naming server URL. There are constants for two well-known servers:
MoeraNaming.MAIN_NAMING_SERVER and MoeraNaming.DEV_NAMING_SERVER. Choose the
server explicitly when creating the fetcher.
import org.moera.lib.jsonrpc.OkHttpJsonRpcFetcher;
import org.moera.lib.naming.MoeraNaming;
import org.moera.lib.naming.types.RegisteredNameInfo;
MoeraNaming naming = new MoeraNaming(
new OkHttpJsonRpcFetcher(MoeraNaming.DEV_NAMING_SERVER)
);
RegisteredNameInfo info = naming.getCurrent("balu-dev", 0);
System.out.println(info.getNodeUri());
A static method MoeraNaming.resolve() is a shortcut that simplifies resolving of names.
import org.moera.lib.jsonrpc.OkHttpJsonRpcFetcher;
import org.moera.lib.naming.MoeraNaming;
System.out.println(MoeraNaming.resolve(
"balu-dev_0",
new OkHttpJsonRpcFetcher(MoeraNaming.DEV_NAMING_SERVER)
));
org.moera.lib.node
package contains classes for accessing Moera nodes. Create a MoeraNode instance
with an OkHttpTransport and the URL of the node, then call the methods you need.
Node API calls can throw MoeraNodeException; the examples assume the surrounding
method catches or declares this exception.
import static org.moera.lib.naming.MoeraNaming.resolve;
import org.moera.lib.http.OkHttpTransport;
import org.moera.lib.jsonrpc.OkHttpJsonRpcFetcher;
import org.moera.lib.naming.MoeraNaming;
import org.moera.lib.node.MoeraNode;
var fetcher = new OkHttpJsonRpcFetcher(MoeraNaming.MAIN_NAMING_SERVER);
MoeraNode node = new MoeraNode(new OkHttpTransport(), resolve("Alice", fetcher));
System.out.println(node.whoAmI().getFullName());
The library automatically parses JSON structures coming from the node and
converts them to Java objects. The corresponding types are defined in
org.moera.lib.naming.types and org.moera.lib.node.types subpackages.
import org.moera.lib.node.types.DomainAttributes;
DomainAttributes attrs = new DomainAttributes();
attrs.setName("dave.moera.club");
attrs.setNodeId(DAVE_NODE_ID);
NodeSourceUri parses and builds node source URIs and their parameters.
import org.moera.lib.node.NodeSourceUri;
NodeSourceUri source = NodeSourceUri.parse(
"https://example/node;via_email=alice%40example.com;label=%D0%9C%D0%B8%D1%80%20%2B"
);
System.out.println(source.getParameter(NodeSourceUri.VIA_EMAIL));
source.setParameter("channel", "social + web");
System.out.println(source);
Read more about authentication in the Node API reference.
To perform an authenticated request, you need to put the root secret, token, or carte
into MoeraNode and activate the corresponding authentication mode. Use
rootSecret(), token(), and carte() methods to set the root secret, token, and
carte correspondingly. The authentication mode is activated by generic
authMethod(), or by shortcut methods noAuth(), auth(), authAdmin(),
authRootAdmin(). After that, all API requests will be authenticated with
the selected method, if needed. You can switch to a different method at any time.
import static org.moera.lib.naming.MoeraNaming.resolve;
import org.moera.lib.http.OkHttpTransport;
import org.moera.lib.jsonrpc.OkHttpJsonRpcFetcher;
import org.moera.lib.naming.MoeraNaming;
import org.moera.lib.node.MoeraNode;
var fetcher = new OkHttpJsonRpcFetcher(MoeraNaming.MAIN_NAMING_SERVER);
MoeraNode node = new MoeraNode(new OkHttpTransport(), resolve("Alice", fetcher));
node.token("putthetokenhere");
node.authAdmin();
System.out.println(node.getProfile(false).getEmail());
MoeraCarteSource
class simplifies managing authentication cartes. It obtains cartes from a home node,
caches them, supplies valid cartes for authentication, and requests new ones
when old ones expire.
import static org.moera.lib.naming.MoeraNaming.resolve;
import org.moera.lib.http.OkHttpTransport;
import org.moera.lib.jsonrpc.OkHttpJsonRpcFetcher;
import org.moera.lib.naming.MoeraNaming;
import org.moera.lib.node.MoeraNode;
import org.moera.lib.node.carte.MoeraCarteSource;
import org.moera.lib.node.types.FeedSliceInfo;
import org.moera.lib.node.types.StoryInfo;
var fetcher = new OkHttpJsonRpcFetcher(MoeraNaming.MAIN_NAMING_SERVER);
var transport = new OkHttpTransport();
MoeraNode home = new MoeraNode(transport, resolve("Alice", fetcher));
home.token("putthetokenhere");
home.authAdmin();
MoeraNode node = new MoeraNode(transport, resolve("Bob", fetcher));
node.carteSource(new MoeraCarteSource(home, null, null, null));
node.auth();
FeedSliceInfo slice = node.getFeedSlice("timeline", null, null, null);
for (StoryInfo story : slice.getStories()) {
if (story.getPosting() != null) {
System.out.println(story.getPosting().getHeading());
}
}
Carte.generate()
static method generates a carte with the given parameters and signs it with
the provided private signing key.
import java.security.interfaces.ECPrivateKey;
import java.time.Instant;
import org.moera.lib.crypto.CryptoUtil;
import org.moera.lib.node.carte.Carte;
import org.moera.lib.node.types.Scope;
byte[] rawKey = new byte[] {
(byte)0x72, (byte)0xd0, (byte)0x81, (byte)0x7b,
(byte)0xea, (byte)0xf1, (byte)0x80, (byte)0x0c,
(byte)0x54, (byte)0x48, (byte)0x84, (byte)0x1e,
(byte)0x49, (byte)0x01, (byte)0x39, (byte)0xb6,
(byte)0x80, (byte)0xf1, (byte)0x34, (byte)0xa5,
(byte)0x6e, (byte)0x14, (byte)0x0b, (byte)0xdb,
(byte)0x4f, (byte)0x33, (byte)0xae, (byte)0xb2,
(byte)0xc4, (byte)0x3e, (byte)0x3c, (byte)0x48
};
ECPrivateKey signingKey = CryptoUtil.rawToPrivateKey(rawKey);
String carte = Carte.generate(
"app0_0",
null,
Instant.now(),
signingKey,
"Alice",
Scope.ALL.getMask(),
Scope.NONE.getMask()
);
org.moera.lib.UniversalLocation
class represents a Moera universal URL.
To parse a universal URL, pass it to UniversalLocation constructor.
The constructed instance will contain the result of parsing.
import java.net.URISyntaxException;
import org.moera.lib.UniversalLocation;
try {
UniversalLocation uni = new UniversalLocation(
"https://moera.page/@Alice/alice.moera.blog/post/69a403ef-b72d-43e0-967e-eab5e8dce9d3"
);
System.out.printf("%s %s %s %s%n", uni.getNodeName(), uni.getAuthority(), uni.getPath(), uni);
} catch (URISyntaxException e) {
// error processing
}
To build a universal URL from parts, use redirectTo() static method.
import org.moera.lib.UniversalLocation;
System.out.println(UniversalLocation.redirectTo(
"Alice_0",
"https://alice.moera.blog/",
"/post/69a403ef-b72d-43e0-967e-eab5e8dce9d3",
null,
null
));
redirectTo() method with two parameters converts URL of a page on a node to
a corresponding universal URL.
import org.moera.lib.UniversalLocation;
System.out.println(UniversalLocation.redirectTo(
"Alice_0",
"https://alice.moera.blog/moera/post/69a403ef-b72d-43e0-967e-eab5e8dce9d3"
));
org.moera.lib.crypto.CryptoUtil
is a utility class for generation of cryptographic keys, signing
fingerprints and validating
signatures. The class uses the algorithms
defined in the Moera protocol.
Static methods for building fingerprints for various objects are defined in
org.moera.lib.naming.Fingerprints
and org.moera.lib.node.Fingerprints
classes. (The methods may have several versions for different versions of
the fingerprint.)
The following code changes a node URL on the naming server. (The user should provide mnemonic of the updating key.)
import java.security.interfaces.ECPrivateKey;
import org.moera.lib.crypto.CryptoUtil;
import org.moera.lib.jsonrpc.OkHttpJsonRpcFetcher;
import org.moera.lib.naming.Fingerprints;
import org.moera.lib.naming.MoeraNaming;
import org.moera.lib.naming.types.RegisteredNameInfo;
import org.moera.lib.util.Util;
ECPrivateKey privateUpdatingKey = CryptoUtil.mnemonicToPrivateKey(mnemonic);
MoeraNaming srv = new MoeraNaming(
new OkHttpJsonRpcFetcher(MoeraNaming.MAIN_NAMING_SERVER)
);
RegisteredNameInfo info = srv.getCurrent(name, generation);
byte[] fingerprint = Fingerprints.putCall(
name,
generation,
info.getUpdatingKey(),
newNodeUri,
info.getSigningKey(),
Util.toTimestamp(info.getValidFrom()),
info.getDigest()
);
byte[] signature = CryptoUtil.sign(fingerprint, privateUpdatingKey);
srv.put(name, generation, null, newNodeUri, null, null, info.getDigest(), signature);