Logo Decentralized Social Network

Java Library

moeralib (GitHub) is a library that allows Java applications to easily interact with the Moera network.

Installation

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.

Maven

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>

Gradle

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.

Naming API

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)
));

Node API

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);

Node source URIs

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);

Authentication

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());

Managing cartes

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());
    }
}

Generating cartes

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()
);

Universal URLs

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"
));

Cryptography

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);