feat: kotlin android example (#226)
* feat: kotlin android example * Adding lightpush and store to kotlin example
|
@ -7,9 +7,10 @@ nodekey
|
|||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.aar
|
||||
*.jar
|
||||
|
||||
# output binaries
|
||||
main
|
||||
go-waku
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
|
|
10
Makefile
|
@ -88,7 +88,7 @@ build-example-c-bindings:
|
|||
|
||||
build-example: build-example-basic2 build-example-chat-2 build-example-filter2 build-example-c-bindings
|
||||
|
||||
static-library: ##@cross-compile Build go-waku as static library for current platform
|
||||
static-library:
|
||||
@echo "Building static library..."
|
||||
go build \
|
||||
-buildmode=c-archive \
|
||||
|
@ -97,7 +97,7 @@ static-library: ##@cross-compile Build go-waku as static library for current pla
|
|||
@echo "Static library built:"
|
||||
@ls -la ./build/lib/libgowaku.*
|
||||
|
||||
dynamic-library: ##@cross-compile Build status-go as shared library for current platform
|
||||
dynamic-library:
|
||||
@echo "Building shared library..."
|
||||
$(GOBIN_SHARED_LIB_CFLAGS) $(GOBIN_SHARED_LIB_CGO_LDFLAGS) go build \
|
||||
-buildmode=c-shared \
|
||||
|
@ -111,3 +111,9 @@ ifeq ($(detected_OS),Linux)
|
|||
endif
|
||||
@echo "Shared library built:"
|
||||
@ls -la ./build/lib/libgowaku.*
|
||||
|
||||
mobile-android:
|
||||
gomobile init && \
|
||||
gomobile bind -target=android -ldflags="-s -w" -o ./build/lib/gowaku.aar ./mobile
|
||||
@echo "Android library built:"
|
||||
@ls -la ./build/lib/*.aar ./build/lib/*.jar
|
|
@ -0,0 +1,15 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -0,0 +1 @@
|
|||
Waku
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
<map>
|
||||
<entry key="app/src/main/res/layout/activity_main.xml" value="0.35625" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
<component name="VisualizationToolProject">
|
||||
<option name="state">
|
||||
<ProjectState>
|
||||
<option name="scale" value="0.35625" />
|
||||
</ProjectState>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,36 @@
|
|||
# Android Kotlin Example
|
||||
|
||||
|
||||
## Requirements
|
||||
- Android Studio
|
||||
|
||||
|
||||
## Running this example
|
||||
These instructions should be executed in the terminal:
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/status-im/go-waku.git
|
||||
cd go-waku
|
||||
|
||||
# Set required env variables
|
||||
export ANDROID_NDK_HOME=/path/to/android/ndk
|
||||
export ANDROID_HOME=/path/to/android/sdk/
|
||||
|
||||
# Build the .jar
|
||||
make mobile-android
|
||||
|
||||
# Copy the jar into `libs/` folder
|
||||
cp ./build/lib/gowaku.jar ./examples/android-kotlin/app/libs/.
|
||||
```
|
||||
|
||||
Open the project in Android Studio and run the example app.
|
||||
|
||||
|
||||
## Help wanted!
|
||||
- Is it possible to build go-waku automatically by executing `make mobile-android` and copying the .jar automatically into `libs/` in Android Studio?
|
||||
- Permissions should be requested on runtime
|
||||
- Determine the required permission to fix this:
|
||||
```
|
||||
2022-04-07 19:29:27.542 20042-20068/com.example.waku E/GoLog: 2022-04-07T23:29:27.542Z ERROR basichost basic/basic_host.go:327 failed to resolve local interface addresses {"error": "route ip+net: netlinkrib: permission denied"}
|
||||
```
|
||||
- The example app blocks the main thread and code in general could be improved
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,45 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.waku"
|
||||
minSdk 26
|
||||
targetSdk 32
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.aar'], dir: 'libs')
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package com.example.waku
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.waku", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.waku">
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Waku">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
<uses-permission-sdk-23 android:name="android.permission.INTERNET" />
|
||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
|
||||
<uses-permission-sdk-23 android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
</manifest>
|
|
@ -0,0 +1,14 @@
|
|||
package com.example.waku
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Config(
|
||||
var host: String? = null,
|
||||
var result: Int? = null,
|
||||
var advertiseAddr: String? = null,
|
||||
var nodeKey: String? = null,
|
||||
var keepAliveInterval: Int? = null,
|
||||
var relay: Boolean? = null,
|
||||
var minPeersToPublish: Int? = null
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
package com.example.waku
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Serializable
|
||||
data class JsonResult<T>(val error: String? = null, val result: T? = null)
|
||||
|
||||
inline fun <reified T> handleResponse(response: String): T {
|
||||
val jsonResult = Json.decodeFromString<JsonResult<T>>(response)
|
||||
|
||||
if (jsonResult.error != null)
|
||||
throw Exception(jsonResult.error)
|
||||
|
||||
if (jsonResult.result == null)
|
||||
throw Exception("no result in response")
|
||||
|
||||
return jsonResult.result
|
||||
}
|
||||
|
||||
inline fun handleResponse(response: String) {
|
||||
val jsonResult = Json.decodeFromString<JsonResult<String>>(response)
|
||||
|
||||
if (jsonResult.error != null)
|
||||
throw Exception(jsonResult.error)
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.example.waku
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.example.waku.events.Event
|
||||
import com.example.waku.events.EventHandler
|
||||
import com.example.waku.events.EventType
|
||||
import com.example.waku.events.MessageEvent
|
||||
import com.example.waku.messages.Message
|
||||
import com.example.waku.messages.decodeAsymmetric
|
||||
import gowaku.Gowaku.defaultPubsubTopic
|
||||
|
||||
val alicePrivKey: String = "0x4f012057e1a1458ce34189cb27daedbbe434f3df0825c1949475dec786e2c64e"
|
||||
val alicePubKey: String =
|
||||
"0x0440f05847c4c7166f57ae8ecaaf72d31bddcbca345e26713ca9e26c93fb8362ddcd5ae7f4533ee956428ad08a89cd18b234c2911a3b1c7fbd1c0047610d987302"
|
||||
val bobPrivKey: String = "0xb91d6b2df8fb6ef8b53b51b2b30a408c49d5e2b530502d58ac8f94e5c5de1453"
|
||||
val bobPubKey: String =
|
||||
"0x045eef61a98ba1cf44a2736fac91183ea2bd86e67de20fe4bff467a71249a8a0c05f795dd7f28ced7c15eaa69c89d4212cc4f526ca5e9a62e88008f506d850cccd"
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
val lbl = findViewById<TextView>(R.id.lbl)
|
||||
|
||||
// This configuration and its attributes are optional
|
||||
var c = Config()
|
||||
c.relay = true
|
||||
|
||||
var node = Node(c)
|
||||
|
||||
// A callback must be registered to receive events
|
||||
class MyEventHandler(var lbl: TextView) : EventHandler {
|
||||
override fun handleEvent(evt: Event) {
|
||||
lbl.text =
|
||||
(lbl.text.toString() + ">>> Received a signal: " + evt.type.toString() + "\n")
|
||||
if (evt.type == EventType.Message) {
|
||||
val m = evt as MessageEvent
|
||||
val decodedPayload = m.event.wakuMessage.decodeAsymmetric(bobPrivKey)
|
||||
lbl.text =
|
||||
(lbl.text.toString() + ">>> Message: " + decodedPayload.data.toString(
|
||||
Charsets.UTF_8
|
||||
) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
node.setEventHandler(MyEventHandler(lbl))
|
||||
|
||||
node.start()
|
||||
|
||||
lbl.text = (lbl.text.toString() + ">>> The node peer ID is " + node.peerID() + "\n")
|
||||
|
||||
node.listenAddresses().forEach {
|
||||
lbl.text = (lbl.text.toString() + ">>> Listening on " + it + "\n")
|
||||
}
|
||||
|
||||
lbl.text = (lbl.text.toString() + ">>> Default pubsub topic: " + defaultPubsubTopic() + "\n");
|
||||
|
||||
try {
|
||||
node.connect("/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS")
|
||||
lbl.text = (lbl.text.toString() + ">>> Connected to Peer" + "\n")
|
||||
|
||||
node.peers().forEach {
|
||||
lbl.text = (lbl.text.toString() + ">>> Peer: " + it.peerID + "\n")
|
||||
lbl.text =
|
||||
(lbl.text.toString() + ">>> Protocols: " + it.protocols.joinToString(",") + "\n")
|
||||
lbl.text =
|
||||
(lbl.text.toString() + ">>> Addresses: " + it.addrs.joinToString(",") + "\n")
|
||||
}
|
||||
|
||||
/*var q = StoreQuery();
|
||||
q.pubsubTopic = defaultPubsubTopic();
|
||||
q.pagingOptions = new(3, null, false);
|
||||
val response = node.StoreQuery(q);
|
||||
println(">>> Retrieved " + response.messages.Count + " messages from store");*/
|
||||
|
||||
} catch (ex: Exception) {
|
||||
lbl.text = (lbl.text.toString() + ">>> Could not connect to peer: " + ex.message)
|
||||
}
|
||||
|
||||
node.relaySubscribe()
|
||||
|
||||
for (i in 1..5) {
|
||||
val payload = ("Hello world! - " + i).toByteArray(Charsets.UTF_8)
|
||||
val timestamp = System.currentTimeMillis() * 1000000
|
||||
val contentTopic = ContentTopic("example", 1, "example", "rfc26")
|
||||
val msg = Message(payload, contentTopic, timestamp = timestamp)
|
||||
val messageID = node.relayPublishEncodeAsymmetric(msg, bobPubKey, alicePrivKey)
|
||||
Thread.sleep(1_000)
|
||||
}
|
||||
|
||||
node.relayUnsubscribe()
|
||||
|
||||
node.stop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
package com.example.waku
|
||||
|
||||
import com.example.waku.events.BaseEvent
|
||||
import com.example.waku.events.EventHandler
|
||||
import com.example.waku.events.EventType
|
||||
import com.example.waku.events.MessageEvent
|
||||
import com.example.waku.messages.Message
|
||||
import com.example.waku.store.StoreQuery
|
||||
import com.example.waku.store.StoreResponse
|
||||
import gowaku.Gowaku
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
|
||||
/**
|
||||
* @param c Config containing the options used to initialize a node. It can be `null` to use
|
||||
* defaults. All the keys from the configuration are optional
|
||||
*/
|
||||
class Node(c: Config? = null) {
|
||||
var running: Boolean = false
|
||||
lateinit var signalHandler: gowaku.SignalHandler
|
||||
lateinit var eventHandler: EventHandler
|
||||
|
||||
init {
|
||||
val configJson = Json.encodeToString(c)
|
||||
val response = Gowaku.newNode(configJson)
|
||||
handleResponse(response)
|
||||
|
||||
signalHandler = DefaultEventHandler()
|
||||
Gowaku.setMobileSignalHandler(signalHandler)
|
||||
}
|
||||
|
||||
inner class DefaultEventHandler : gowaku.SignalHandler {
|
||||
override fun handleSignal(signalJson: String) {
|
||||
if (eventHandler != null) {
|
||||
val evt = Json {
|
||||
ignoreUnknownKeys = true; coerceInputValues = true
|
||||
}.decodeFromString<BaseEvent>(signalJson)
|
||||
when (evt.type) {
|
||||
EventType.Message -> {
|
||||
try {
|
||||
val msgEvt = Json.decodeFromString<MessageEvent>(signalJson)
|
||||
eventHandler.handleEvent(msgEvt)
|
||||
} catch (e: Exception) {
|
||||
// TODO: do something
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO: do something with invalid message type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to act as event handler and receive application signals which are used to
|
||||
* react to asyncronous events in waku.
|
||||
* @param handler event handler
|
||||
*/
|
||||
fun Node.setEventHandler(handler: EventHandler) {
|
||||
eventHandler = handler
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a node mounting all the protocols that were enabled during the node instantiation.
|
||||
*/
|
||||
fun Node.start() {
|
||||
if (running) {
|
||||
return
|
||||
}
|
||||
|
||||
val response = Gowaku.start()
|
||||
handleResponse(response)
|
||||
running = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a node
|
||||
*/
|
||||
fun Node.stop() {
|
||||
if (!running) {
|
||||
return
|
||||
}
|
||||
|
||||
val response = Gowaku.stop()
|
||||
handleResponse(response)
|
||||
running = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the peer ID of the go-waku node.
|
||||
* @return The base58 encoded peer Id
|
||||
*/
|
||||
fun Node.peerID(): String {
|
||||
val response = Gowaku.peerID()
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain number of connected peers
|
||||
* @return The number of peers connected to this node
|
||||
*/
|
||||
fun Node.peerCnt(): Int {
|
||||
val response = Gowaku.peerCnt()
|
||||
return handleResponse<Int>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the multiaddresses the wakunode is listening to
|
||||
* @return List of multiaddresses
|
||||
*/
|
||||
fun Node.listenAddresses(): List<String> {
|
||||
val response = Gowaku.listenAddresses()
|
||||
return handleResponse<List<String>>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add node multiaddress and protocol to the wakunode peerstore
|
||||
* @param address multiaddress of the peer being added
|
||||
* @param protocolID protocol supported by the peer
|
||||
* @return Base58 encoded peer Id
|
||||
*/
|
||||
fun Node.addPeer(address: String, protocolID: String): String {
|
||||
val response = Gowaku.addPeer(address, protocolID)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to peer at multiaddress
|
||||
* @param address multiaddress of the peer being dialed
|
||||
* @param ms max duration in milliseconds this function might take to execute. If the function
|
||||
* execution takes longer than this value, the execution will be canceled and an error
|
||||
* returned. Use 0 for unlimited duration
|
||||
*/
|
||||
fun Node.connect(address: String, ms: Long = 0) {
|
||||
val response = Gowaku.connect(address, ms)
|
||||
handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connection to a known peer by peerID
|
||||
* @param peerID Base58 encoded peer ID to disconnect
|
||||
*/
|
||||
fun Node.disconnect(peerID: String) {
|
||||
val response = Gowaku.disconnect(peerID)
|
||||
handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a WakuRelay topic to receive messages
|
||||
* @param topic Pubsub topic to subscribe to. Use NULL for subscribing to the default pubsub topic
|
||||
*/
|
||||
fun Node.relaySubscribe(topic: String? = null) {
|
||||
val response = Gowaku.relaySubscribe(topic)
|
||||
handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message using waku relay
|
||||
* @param msg Message to broadcast
|
||||
* @param topic Pubsub topic. Set to `null` to use the default pubsub topic
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return message id
|
||||
*/
|
||||
fun Node.relayPublish(msg: Message, topic: String? = null, ms: Long = 0): String {
|
||||
val jsonMsg = Json.encodeToString(msg)
|
||||
val response = Gowaku.relayPublish(jsonMsg, topic, ms)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message using waku lightpush
|
||||
* @param msg Message to broadcast
|
||||
* @param topic Pubsub topic. Set to `null` to use the default pubsub topic
|
||||
* @param peerID ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return message id
|
||||
*/
|
||||
fun Node.lightpushPublish(msg: Message, topic: String? = null, peerID: String? = null, ms: Long = 0): String {
|
||||
val jsonMsg = Json.encodeToString(msg)
|
||||
val response = Gowaku.lightpushPublish(jsonMsg, topic, peerID, ms)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message encrypted with an secp256k1 public key using waku relay
|
||||
* @param msg Message to broadcast
|
||||
* @param publicKey Secp256k1 public key
|
||||
* @param optionalSigningKey Optional secp256k1 private key for signing the message
|
||||
* @param topic Pubsub topic. Set to `null` to use the default pubsub topic
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return message id
|
||||
*/
|
||||
fun Node.relayPublishEncodeAsymmetric(
|
||||
msg: Message,
|
||||
publicKey: String,
|
||||
optionalSigningKey: String? = null,
|
||||
topic: String? = null,
|
||||
ms: Long = 0
|
||||
): String {
|
||||
val jsonMsg = Json.encodeToString(msg)
|
||||
val response =
|
||||
Gowaku.relayPublishEncodeAsymmetric(jsonMsg, topic, publicKey, optionalSigningKey, ms)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message encrypted with an secp256k1 public key using waku lightpush
|
||||
* @param msg Message to broadcast
|
||||
* @param publicKey Secp256k1 public key
|
||||
* @param optionalSigningKey Optional secp256k1 private key for signing the message
|
||||
* @param topic Pubsub topic. Set to `null` to use the default pubsub topic
|
||||
* @param peerID ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return message id
|
||||
*/
|
||||
fun Node.lightpushPublishEncodeAsymmetric(
|
||||
msg: Message,
|
||||
publicKey: String,
|
||||
optionalSigningKey: String? = null,
|
||||
topic: String? = null,
|
||||
peerID: String? = null,
|
||||
ms: Long = 0
|
||||
): String {
|
||||
val jsonMsg = Json.encodeToString(msg)
|
||||
val response =
|
||||
Gowaku.lightpushPublishEncodeAsymmetric(jsonMsg, topic, peerID, publicKey, optionalSigningKey, ms)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message encrypted with a 32 byte symmetric key using waku relay
|
||||
* @param msg Message to broadcast
|
||||
* @param symmetricKey 32 byte hex string containing a symmetric key
|
||||
* @param optionalSigningKey Optional secp256k1 private key for signing the message
|
||||
* @param topic Pubsub topic. Set to `null` to use the default pubsub topic
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return message id
|
||||
*/
|
||||
fun Node.relayPublishEncodeSymmetric(
|
||||
msg: Message,
|
||||
symmetricKey: String,
|
||||
optionalSigningKey: String? = null,
|
||||
topic: String? = null,
|
||||
ms: Long = 0
|
||||
): String {
|
||||
val jsonMsg = Json.encodeToString(msg)
|
||||
val response =
|
||||
Gowaku.relayPublishEncodeSymmetric(jsonMsg, topic, symmetricKey, optionalSigningKey, ms)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message encrypted with a 32 byte symmetric key using waku lightpush
|
||||
* @param msg Message to broadcast
|
||||
* @param symmetricKey 32 byte hex string containing a symmetric key
|
||||
* @param optionalSigningKey Optional secp256k1 private key for signing the message
|
||||
* @param topic Pubsub topic. Set to `null` to use the default pubsub topic
|
||||
* @param peerID ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return message id
|
||||
*/
|
||||
fun Node.lightpushPublishEncodeSymmetric(
|
||||
msg: Message,
|
||||
symmetricKey: String,
|
||||
optionalSigningKey: String? = null,
|
||||
topic: String? = null,
|
||||
peerID: String? = null,
|
||||
ms: Long = 0
|
||||
): String {
|
||||
val jsonMsg = Json.encodeToString(msg)
|
||||
val response =
|
||||
Gowaku.lightpushPublishEncodeSymmetric(jsonMsg, topic, peerID, symmetricKey, optionalSigningKey, ms)
|
||||
return handleResponse<String>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are enough peers to publish a message on a topic
|
||||
* @param topic pubsub topic to verify. Use NULL to verify the number of peers in the default pubsub topic
|
||||
* @return boolean indicating if there are enough peers or not
|
||||
*/
|
||||
fun Node.relayEnoughPeers(topic: String? = null): Boolean {
|
||||
val response = Gowaku.relayEnoughPeers(topic)
|
||||
return handleResponse<Boolean>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the pubsub subscription to a pubsub topic
|
||||
* @param topic Pubsub topic to unsubscribe. Use NULL for unsubscribe from the default pubsub topic
|
||||
*/
|
||||
fun Node.relayUnsubscribe(topic: String? = null) {
|
||||
val response = Gowaku.relayUnsubscribe(topic)
|
||||
handleResponse(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get peers
|
||||
* @return Retrieve list of peers and their supported protocols
|
||||
*/
|
||||
fun Node.peers(): List<Peer> {
|
||||
val response = Gowaku.peers()
|
||||
return handleResponse<List<Peer>>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query message history
|
||||
* @param query Query
|
||||
* @param peerID PeerID to ask the history from. Use NULL to automatically select a peer
|
||||
* @param ms If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
* (in milliseconds) is reached, or an error will be returned
|
||||
* @return Response containing the messages and cursor for pagination. Use the cursor in further queries to retrieve more results
|
||||
*/
|
||||
fun Node.storeQuery(query: StoreQuery, peerID: String?, ms: Long = 0): StoreResponse{
|
||||
val queryJSON = Json.encodeToString(query)
|
||||
val response = Gowaku.storeQuery(queryJSON, peerID, ms)
|
||||
return handleResponse<StoreResponse>(response)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.example.waku
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Peer(
|
||||
val peerID: String?,
|
||||
val connected: Boolean,
|
||||
val protocols: List<String>,
|
||||
val addrs: List<String>,
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
package com.example.waku
|
||||
|
||||
import gowaku.Gowaku
|
||||
|
||||
/**
|
||||
* Get default pubsub topic
|
||||
* @return Default pubsub topic used for exchanging waku messages defined in RFC 10
|
||||
*/
|
||||
fun DefaultPubsubTopic(): String {
|
||||
return Gowaku.defaultPubsubTopic()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a content topic string
|
||||
* @param applicationName
|
||||
* @param applicationVersion
|
||||
* @param contentTopicName
|
||||
* @param encoding
|
||||
* @return Content topic string according to RFC 23
|
||||
*/
|
||||
fun ContentTopic(applicationName: String, applicationVersion: Long, contentTopicName: String, encoding: String): String{
|
||||
return Gowaku.contentTopic(applicationName, applicationVersion, contentTopicName, encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pubsub topic string
|
||||
* @param name
|
||||
* @param encoding
|
||||
* @return Pubsub topic string according to RFC 23
|
||||
*/
|
||||
fun PubsubTopic(name: String, encoding: String): String {
|
||||
return Gowaku.pubsubTopic(name, encoding)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.example.waku.events
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class BaseEvent(override val type: EventType) : Event
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.waku.events
|
||||
|
||||
interface Event {
|
||||
val type: EventType
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.waku.events
|
||||
|
||||
interface EventHandler {
|
||||
fun handleEvent(evt: Event)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.example.waku.events
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class EventType {
|
||||
@SerialName("unknown")
|
||||
Unknown,
|
||||
@SerialName("message")
|
||||
Message
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.example.waku.events
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MessageEvent(override val type: EventType, val event: MessageEventData) : Event
|
|
@ -0,0 +1,7 @@
|
|||
package com.example.waku.events
|
||||
|
||||
import com.example.waku.messages.Message
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MessageEventData(val messageID: String, val pubsubTopic: String, val wakuMessage: Message)
|
|
@ -0,0 +1,12 @@
|
|||
package com.example.waku.messages
|
||||
|
||||
import com.example.waku.serializers.ByteArrayBase64Serializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DecodedPayload(
|
||||
val pubkey: String?,
|
||||
val signature: String?,
|
||||
@Serializable(with = ByteArrayBase64Serializer::class) val data: ByteArray,
|
||||
val padding: String?
|
||||
)
|
|
@ -0,0 +1,40 @@
|
|||
package com.example.waku.messages
|
||||
|
||||
import com.example.waku.handleResponse
|
||||
import com.example.waku.serializers.ByteArrayBase64Serializer
|
||||
import gowaku.Gowaku
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Serializable
|
||||
data class Message(
|
||||
@Serializable(with = ByteArrayBase64Serializer::class) val payload: ByteArray,
|
||||
val contentTopic: String? = "",
|
||||
val version: Int? = 0,
|
||||
val timestamp: Long? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Decode a waku message using an asymmetric key
|
||||
* @param msg Message to decode
|
||||
* @param privateKey Secp256k1 private key used to decode the message
|
||||
* @return DecodedPayload containing the decrypted message, padding, public key and signature (if available)
|
||||
*/
|
||||
fun Message.decodeAsymmetric(privateKey: String): DecodedPayload {
|
||||
val jsonMsg = Json.encodeToString(this)
|
||||
val response = Gowaku.decodeAsymmetric(jsonMsg, privateKey)
|
||||
return handleResponse<DecodedPayload>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a waku message using a symmetric key
|
||||
* @param msg Message to decode
|
||||
* @param privateKey Symmetric key used to decode the message
|
||||
* @return DecodedPayload containing the decrypted message, padding, public key and signature (if available)
|
||||
*/
|
||||
fun Message.decodeSymmetric(symmetricKey: String): DecodedPayload {
|
||||
val jsonMsg = Json.encodeToString(this)
|
||||
val response = Gowaku.decodeSymmetric(jsonMsg, symmetricKey)
|
||||
return handleResponse<DecodedPayload>(response)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.example.waku.serializers
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.*
|
||||
|
||||
@Serializer(forClass = ByteArray::class)
|
||||
object ByteArrayBase64Serializer : KSerializer<ByteArray> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("ByteArrayBase64Serializer", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ByteArray) {
|
||||
encoder.encodeString(Base64.getEncoder().encodeToString(value))
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): ByteArray {
|
||||
val text = decoder.decodeString()
|
||||
return Base64.getDecoder().decode(text)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.example.waku.store
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ContentFilter(val contentTopic: String)
|
|
@ -0,0 +1,13 @@
|
|||
package com.example.waku.store
|
||||
|
||||
import com.example.waku.DefaultPubsubTopic
|
||||
import com.example.waku.serializers.ByteArrayBase64Serializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Cursor(
|
||||
@Serializable(with = ByteArrayBase64Serializer::class) val digest: ByteArray,
|
||||
val pubsubTopic: String = DefaultPubsubTopic(),
|
||||
val receiverTime: Long = 0,
|
||||
val senderTime: Long = 0
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
package com.example.waku.store
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PagingOptions(val pageSize: Int, val cursor: Cursor?, val forward: Boolean)
|
|
@ -0,0 +1,13 @@
|
|||
package com.example.waku.store
|
||||
|
||||
import com.example.waku.DefaultPubsubTopic
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StoreQuery(
|
||||
var pubsubTopic: String? = DefaultPubsubTopic(),
|
||||
var startTime: Long? = null,
|
||||
var endTime: Long? = null,
|
||||
var contentFilter: List<ContentFilter>?,
|
||||
var pagingOptions: PagingOptions?
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
package com.example.waku.store
|
||||
|
||||
import com.example.waku.messages.Message
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StoreResponse(val messages: List<Message>, val pagingOptions: PagingOptions?)
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lbl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 7.6 KiB |
|
@ -0,0 +1,16 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Waku" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Waku</string>
|
||||
</resources>
|
|
@ -0,0 +1,16 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Waku" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,17 @@
|
|||
package com.example.waku
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.0' apply false
|
||||
id 'com.android.library' version '7.1.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.20'
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
|
@ -0,0 +1,6 @@
|
|||
#Tue Apr 05 10:08:00 AST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,89 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,16 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "Waku"
|
||||
include ':app'
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -23,7 +23,7 @@ void SignalHandler(Waku.Event evt)
|
|||
if (evt.type == Waku.EventType.Message)
|
||||
{
|
||||
Waku.MessageEvent msgEvt = (Waku.MessageEvent)evt; // Downcast to specific event type to access the event data
|
||||
Waku.DecodedPayload decodedPayload = node.RelayPublishDecodeAsymmetric(msgEvt.data.wakuMessage, bobPrivKey);
|
||||
Waku.DecodedPayload decodedPayload = node.DecodeAsymmetric(msgEvt.data.wakuMessage, bobPrivKey);
|
||||
|
||||
string message = Encoding.UTF8.GetString(decodedPayload.data);
|
||||
Console.WriteLine(">>> Message: " + message + " from: " + decodedPayload.pubkey);
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace Waku
|
|||
public string? nodeKey { get; set; }
|
||||
public int? keepAliveInterval { get; set; }
|
||||
public bool? relay { get; set; }
|
||||
public int? minPeersToPublish {get; set; }
|
||||
}
|
||||
|
||||
public enum EventType
|
||||
|
@ -45,8 +46,6 @@ namespace Waku
|
|||
|
||||
public class Event
|
||||
{
|
||||
public int nodeId { get; set; }
|
||||
|
||||
public EventType type { get; set; } = EventType.Unknown;
|
||||
}
|
||||
|
||||
|
@ -54,8 +53,6 @@ namespace Waku
|
|||
{
|
||||
public string messageID { get; set; } = "";
|
||||
|
||||
public string subscriptionID { get; set; } = "";
|
||||
|
||||
public string pubsubTopic { get; set; } = Utils.DefaultPubsubTopic();
|
||||
|
||||
public Message wakuMessage { get; set; } = new Message();
|
||||
|
@ -194,10 +191,14 @@ namespace Waku
|
|||
internal static extern IntPtr waku_start();
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a go-waku node mounting all the protocols that were enabled during the waku node initialization.
|
||||
/// Initialize a go-waku node mounting all the protocols that were enabled during the waku node instantiation.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if(_running) {
|
||||
return
|
||||
}
|
||||
|
||||
IntPtr ptr = waku_start();
|
||||
Response.HandleResponse(ptr);
|
||||
|
||||
|
@ -212,6 +213,10 @@ namespace Waku
|
|||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if(!_running) {
|
||||
return
|
||||
}
|
||||
|
||||
IntPtr ptr = waku_stop();
|
||||
Response.HandleResponse(ptr);
|
||||
|
||||
|
@ -331,6 +336,23 @@ namespace Waku
|
|||
return Response.HandleResponse(ptr, "could not obtain the message id");
|
||||
}
|
||||
|
||||
[DllImport(Constants.dllName)]
|
||||
internal static extern IntPtr waku_lightpush_publish(string messageJSON, string? topic, string? peerID, int ms);
|
||||
|
||||
/// <summary>
|
||||
/// Publish a message using waku lightpush.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to broadcast</param>
|
||||
/// <param name="topic">Pubsub topic. Set to `null` to use the default pubsub topic</param>
|
||||
/// <param name="peerID">ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node</param>
|
||||
/// <param name="ms">If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned</param>
|
||||
/// <returns></returns>
|
||||
public string LightpushPublish(Message msg, string? topic = null, string? peerID = null, int ms = 0)
|
||||
{
|
||||
string jsonMsg = JsonSerializer.Serialize(msg);
|
||||
IntPtr ptr = waku_lightpush_publish(jsonMsg, topic, peerID, ms);
|
||||
return Response.HandleResponse(ptr, "could not obtain the message id");
|
||||
}
|
||||
|
||||
[DllImport(Constants.dllName)]
|
||||
internal static extern IntPtr waku_relay_publish_enc_asymmetric(string messageJSON, string? topic, string publicKey, string? optionalSigningKey, int ms);
|
||||
|
@ -351,6 +373,26 @@ namespace Waku
|
|||
return Response.HandleResponse(ptr, "could not obtain the message id");
|
||||
}
|
||||
|
||||
[DllImport(Constants.dllName)]
|
||||
internal static extern IntPtr waku_lightpush_publish_enc_asymmetric(string messageJSON, string? topic, string? peerID, string publicKey, string? optionalSigningKey, int ms);
|
||||
|
||||
/// <summary>
|
||||
/// Publish a message encrypted with an secp256k1 public key using waku lightpush.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to broadcast</param>
|
||||
/// <param name="publicKey">Secp256k1 public key</param>
|
||||
/// <param name="optionalSigningKey">Optional secp256k1 private key for signing the message</param>
|
||||
/// <param name="topic">Pubsub topic. Set to `null` to use the default pubsub topic</param>
|
||||
/// <param name="peerID">ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node</param>
|
||||
/// <param name="ms">If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned</param>
|
||||
/// <returns></returns>
|
||||
public string LightpushPublishEncodeAsymmetric(Message msg, string publicKey, string? optionalSigningKey = null, string? topic = null, string? peerID = null, int ms = 0)
|
||||
{
|
||||
string jsonMsg = JsonSerializer.Serialize(msg);
|
||||
IntPtr ptr = waku_lightpush_publish_enc_asymmetric(jsonMsg, topic, peerID, publicKey, optionalSigningKey, ms);
|
||||
return Response.HandleResponse(ptr, "could not obtain the message id");
|
||||
}
|
||||
|
||||
[DllImport(Constants.dllName)]
|
||||
internal static extern IntPtr waku_relay_publish_enc_symmetric(string messageJSON, string? topic, string symmetricKey, string? optionalSigningKey, int ms);
|
||||
|
||||
|
@ -370,6 +412,26 @@ namespace Waku
|
|||
return Response.HandleResponse(ptr, "could not obtain the message id");
|
||||
}
|
||||
|
||||
[DllImport(Constants.dllName)]
|
||||
internal static extern IntPtr waku_lightpush_publish_enc_symmetric(string messageJSON, string? topic, string? peerID, string symmetricKey, string? optionalSigningKey, int ms);
|
||||
|
||||
/// <summary>
|
||||
/// Publish a message encrypted with a 32 bytes symmetric key using waku lightpush.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message to broadcast</param>
|
||||
/// <param name="symmetricKey">32 byte hex string containing a symmetric key</param>
|
||||
/// <param name="optionalSigningKey">Optional secp256k1 private key for signing the message</param>
|
||||
/// <param name="topic">Pubsub topic. Set to `null` to use the default pubsub topic</param>
|
||||
/// <param name="peerID">ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node</param>
|
||||
/// <param name="ms">If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned</param>
|
||||
/// <returns></returns>
|
||||
public string RelayPublishEncodeSymmetric(Message msg, string symmetricKey, string? optionalSigningKey = null, string? topic = null, string? peerID = null, int ms = 0)
|
||||
{
|
||||
string jsonMsg = JsonSerializer.Serialize(msg);
|
||||
IntPtr ptr = waku_lightpush_publish_enc_symmetric(jsonMsg, topic, peerID, symmetricKey, optionalSigningKey, ms);
|
||||
return Response.HandleResponse(ptr, "could not obtain the message id");
|
||||
}
|
||||
|
||||
[DllImport(Constants.dllName)]
|
||||
internal static extern IntPtr waku_decode_symmetric(string messageJSON, string symmetricKey);
|
||||
|
||||
|
@ -379,7 +441,7 @@ namespace Waku
|
|||
/// <param name="msg">Message to decode</param>
|
||||
/// <param name="symmetricKey">Symmetric key used to decode the message</param>
|
||||
/// <returns>DecodedPayload containing the decrypted message, padding, public key and signature (if available)</returns>
|
||||
public DecodedPayload RelayPublishDecodeSymmetric(Message msg, string symmetricKey)
|
||||
public DecodedPayload DecodeSymmetric(Message msg, string symmetricKey)
|
||||
{
|
||||
string jsonMsg = JsonSerializer.Serialize(msg);
|
||||
IntPtr ptr = waku_decode_symmetric(jsonMsg, symmetricKey);
|
||||
|
@ -395,7 +457,7 @@ namespace Waku
|
|||
/// <param name="msg">Message to decode</param>
|
||||
/// <param name="privateKey">Secp256k1 private key used to decode the message</param>
|
||||
/// <returns>DecodedPayload containing the decrypted message, padding, public key and signature (if available)</returns>
|
||||
public DecodedPayload RelayPublishDecodeAsymmetric(Message msg, string privateKey)
|
||||
public DecodedPayload DecodeAsymmetric(Message msg, string privateKey)
|
||||
{
|
||||
string jsonMsg = JsonSerializer.Serialize(msg);
|
||||
IntPtr ptr = waku_decode_asymmetric(jsonMsg, privateKey);
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace Waku
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static T HandleResponse<T>(IntPtr ptr, string errNoValue) where T : struct
|
||||
{
|
||||
string strResponse = PtrToStringUtf8(ptr);
|
||||
|
|
1
go.sum
|
@ -1136,6 +1136,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
|
|||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
|
|
413
library/api.go
|
@ -6,96 +6,14 @@ package main
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
p2pproto "github.com/libp2p/go-libp2p-core/protocol"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/status-im/go-waku/waku/v2/node"
|
||||
mobile "github.com/status-im/go-waku/mobile"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
)
|
||||
|
||||
var wakuNode *node.WakuNode
|
||||
|
||||
var ErrWakuNodeNotReady = errors.New("go-waku not initialized")
|
||||
|
||||
func randomHex(n int) (string, error) {
|
||||
bytes := make([]byte, n)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
type WakuConfig struct {
|
||||
Host *string `json:"host,omitempty"`
|
||||
Port *int `json:"port,omitempty"`
|
||||
AdvertiseAddress *string `json:"advertiseAddr,omitempty"`
|
||||
NodeKey *string `json:"nodeKey,omitempty"`
|
||||
KeepAliveInterval *int `json:"keepAliveInterval,omitempty"`
|
||||
EnableRelay *bool `json:"relay"`
|
||||
MinPeersToPublish *int `json:"minPeersToPublish"`
|
||||
}
|
||||
|
||||
var DefaultHost = "0.0.0.0"
|
||||
var DefaultPort = 60000
|
||||
var DefaultKeepAliveInterval = 20
|
||||
var DefaultEnableRelay = true
|
||||
var DefaultMinPeersToPublish = 0
|
||||
|
||||
func getConfig(configJSON *C.char) (WakuConfig, error) {
|
||||
var config WakuConfig
|
||||
if configJSON != nil {
|
||||
err := json.Unmarshal([]byte(C.GoString(configJSON)), &config)
|
||||
if err != nil {
|
||||
return WakuConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.Host == nil {
|
||||
config.Host = &DefaultHost
|
||||
}
|
||||
|
||||
if config.EnableRelay == nil {
|
||||
config.EnableRelay = &DefaultEnableRelay
|
||||
}
|
||||
|
||||
if config.Host == nil {
|
||||
config.Host = &DefaultHost
|
||||
}
|
||||
|
||||
if config.Port == nil {
|
||||
config.Port = &DefaultPort
|
||||
}
|
||||
|
||||
if config.KeepAliveInterval == nil {
|
||||
config.KeepAliveInterval = &DefaultKeepAliveInterval
|
||||
}
|
||||
|
||||
if config.MinPeersToPublish == nil {
|
||||
config.MinPeersToPublish = &DefaultMinPeersToPublish
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
//export waku_new
|
||||
// Initialize a waku node. Receives a JSON string containing the configuration
|
||||
// for the node. It can be NULL. Example configuration:
|
||||
|
@ -112,198 +30,71 @@ func getConfig(configJSON *C.char) (WakuConfig, error) {
|
|||
// This function will return a nodeID which should be used in all calls from this API that require
|
||||
// interacting with the node.
|
||||
func waku_new(configJSON *C.char) *C.char {
|
||||
if wakuNode != nil {
|
||||
return makeJSONResponse(errors.New("go-waku already initialized. stop it first"))
|
||||
}
|
||||
|
||||
config, err := getConfig(configJSON)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hostAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", *config.Host, *config.Port))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
var prvKey *ecdsa.PrivateKey
|
||||
if config.NodeKey != nil {
|
||||
prvKey, err = crypto.HexToECDSA(*config.NodeKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
} else {
|
||||
key, err := randomHex(32)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
prvKey, err = crypto.HexToECDSA(key)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
}
|
||||
|
||||
opts := []node.WakuNodeOption{
|
||||
node.WithPrivateKey(prvKey),
|
||||
node.WithHostAddress(hostAddr),
|
||||
node.WithKeepAlive(time.Duration(*config.KeepAliveInterval) * time.Second),
|
||||
}
|
||||
|
||||
if *config.EnableRelay {
|
||||
opts = append(opts, node.WithWakuRelayAndMinPeers(*config.MinPeersToPublish))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
w, err := node.New(ctx, opts...)
|
||||
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
wakuNode = w
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
response := mobile.NewNode(C.GoString(configJSON))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_start
|
||||
// Starts the waku node
|
||||
func waku_start() *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
if err := wakuNode.Start(); err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
response := mobile.Start()
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_stop
|
||||
// Stops a waku node
|
||||
func waku_stop() *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
wakuNode.Stop()
|
||||
wakuNode = nil
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
response := mobile.Stop()
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_peerid
|
||||
// Obtain the peer ID of the waku node
|
||||
func waku_peerid() *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(wakuNode.ID(), nil)
|
||||
response := mobile.PeerID()
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_listen_addresses
|
||||
// Obtain the multiaddresses the wakunode is listening to
|
||||
func waku_listen_addresses() *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var addresses []string
|
||||
for _, addr := range wakuNode.ListenAddresses() {
|
||||
addresses = append(addresses, addr.String())
|
||||
}
|
||||
|
||||
return prepareJSONResponse(addresses, nil)
|
||||
response := mobile.ListenAddresses()
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_add_peer
|
||||
// Add node multiaddress and protocol to the wakunode peerstore
|
||||
func waku_add_peer(address *C.char, protocolID *C.char) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
ma, err := multiaddr.NewMultiaddr(C.GoString(address))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
peerID, err := wakuNode.AddPeer(ma, p2pproto.ID(C.GoString(protocolID)))
|
||||
return prepareJSONResponse(peerID, err)
|
||||
response := mobile.AddPeer(C.GoString(address), C.GoString(protocolID))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_connect
|
||||
// Connect to peer at multiaddress. if ms > 0, cancel the function execution if it takes longer than N milliseconds
|
||||
func waku_connect(address *C.char, ms C.int) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
err := wakuNode.DialPeer(ctx, C.GoString(address))
|
||||
return makeJSONResponse(err)
|
||||
response := mobile.Connect(C.GoString(address), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_connect_peerid
|
||||
// Connect to known peer by peerID. if ms > 0, cancel the function execution if it takes longer than N milliseconds
|
||||
func waku_connect_peerid(peerID *C.char, ms C.int) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
pID, err := peer.Decode(C.GoString(peerID))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
err = wakuNode.DialPeerByID(ctx, pID)
|
||||
return makeJSONResponse(err)
|
||||
response := mobile.Connect(C.GoString(peerID), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_disconnect
|
||||
// Close connection to a known peer by peerID
|
||||
func waku_disconnect(peerID *C.char) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
pID, err := peer.Decode(C.GoString(peerID))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
err = wakuNode.ClosePeerById(pID)
|
||||
return makeJSONResponse(err)
|
||||
response := mobile.Disconnect(C.GoString(peerID))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_peer_cnt
|
||||
// Get number of connected peers
|
||||
func waku_peer_cnt() *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(wakuNode.PeerCount(), nil)
|
||||
response := mobile.PeerCnt()
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_content_topic
|
||||
|
@ -315,23 +106,13 @@ func waku_content_topic(applicationName *C.char, applicationVersion C.uint, cont
|
|||
//export waku_pubsub_topic
|
||||
// Create a pubsub topic string according to RFC 23
|
||||
func waku_pubsub_topic(name *C.char, encoding *C.char) *C.char {
|
||||
return prepareJSONResponse(protocol.NewPubsubTopic(C.GoString(name), C.GoString(encoding)).String(), nil)
|
||||
return C.CString(mobile.PubsubTopic(C.GoString(name), C.GoString(encoding)))
|
||||
}
|
||||
|
||||
//export waku_default_pubsub_topic
|
||||
// Get the default pubsub topic used in waku2: /waku/2/default-waku/proto
|
||||
func waku_default_pubsub_topic() *C.char {
|
||||
return C.CString(protocol.DefaultPubsubTopic().String())
|
||||
}
|
||||
|
||||
func getTopic(topic *C.char) string {
|
||||
result := ""
|
||||
if topic != nil {
|
||||
result = C.GoString(topic)
|
||||
} else {
|
||||
result = protocol.DefaultPubsubTopic().String()
|
||||
}
|
||||
return result
|
||||
return C.CString(mobile.DefaultPubsubTopic())
|
||||
}
|
||||
|
||||
//export waku_set_event_callback
|
||||
|
@ -339,160 +120,26 @@ func getTopic(topic *C.char) string {
|
|||
// (in JSON) which are used o react to asyncronous events in waku. The function
|
||||
// signature for the callback should be `void myCallback(char* signalJSON)`
|
||||
func waku_set_event_callback(cb unsafe.Pointer) {
|
||||
setEventCallback(cb)
|
||||
}
|
||||
|
||||
type SubscriptionMsg struct {
|
||||
MessageID string `json:"messageID"`
|
||||
PubsubTopic string `json:"pubsubTopic"`
|
||||
Message *pb.WakuMessage `json:"wakuMessage"`
|
||||
}
|
||||
|
||||
func toSubscriptionMessage(msg *protocol.Envelope) *SubscriptionMsg {
|
||||
return &SubscriptionMsg{
|
||||
MessageID: hexutil.Encode(msg.Hash()),
|
||||
PubsubTopic: msg.PubsubTopic(),
|
||||
Message: msg.Message(),
|
||||
}
|
||||
mobile.SetEventCallback(cb)
|
||||
}
|
||||
|
||||
//export waku_peers
|
||||
// Retrieve the list of peers known by the waku node
|
||||
func waku_peers() *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
peers, err := wakuNode.Peers()
|
||||
return prepareJSONResponse(peers, err)
|
||||
}
|
||||
|
||||
func unmarshalPubkey(pub []byte) (ecdsa.PublicKey, error) {
|
||||
x, y := elliptic.Unmarshal(secp256k1.S256(), pub)
|
||||
if x == nil {
|
||||
return ecdsa.PublicKey{}, errors.New("invalid public key")
|
||||
}
|
||||
return ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}, nil
|
||||
response := mobile.Peers()
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_decode_symmetric
|
||||
// Decode a waku message using a 32 bytes symmetric key. The key must be a hex encoded string with "0x" prefix
|
||||
func waku_decode_symmetric(messageJSON *C.char, symmetricKey *C.char) *C.char {
|
||||
var msg pb.WakuMessage
|
||||
err := json.Unmarshal([]byte(C.GoString(messageJSON)), &msg)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if msg.Version == 0 {
|
||||
return prepareJSONResponse(msg.Payload, nil)
|
||||
} else if msg.Version > 1 {
|
||||
return makeJSONResponse(errors.New("unsupported wakumessage version"))
|
||||
}
|
||||
|
||||
keyInfo := &node.KeyInfo{
|
||||
Kind: node.Symmetric,
|
||||
}
|
||||
|
||||
keyInfo.SymKey, err = hexutil.Decode(C.GoString(symmetricKey))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
payload, err := node.DecodePayload(&msg, keyInfo)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
PubKey string `json:"pubkey"`
|
||||
Signature string `json:"signature"`
|
||||
Data []byte `json:"data"`
|
||||
Padding []byte `json:"padding"`
|
||||
}{
|
||||
PubKey: hexutil.Encode(crypto.FromECDSAPub(payload.PubKey)),
|
||||
Signature: hexutil.Encode(payload.Signature),
|
||||
Data: payload.Data,
|
||||
Padding: payload.Padding,
|
||||
}
|
||||
|
||||
return prepareJSONResponse(response, err)
|
||||
response := mobile.DecodeSymmetric(C.GoString(messageJSON), C.GoString(symmetricKey))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_decode_asymmetric
|
||||
// Decode a waku message using a secp256k1 private key. The key must be a hex encoded string with "0x" prefix
|
||||
func waku_decode_asymmetric(messageJSON *C.char, privateKey *C.char) *C.char {
|
||||
var msg pb.WakuMessage
|
||||
err := json.Unmarshal([]byte(C.GoString(messageJSON)), &msg)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if msg.Version == 0 {
|
||||
return prepareJSONResponse(msg.Payload, nil)
|
||||
} else if msg.Version > 1 {
|
||||
return makeJSONResponse(errors.New("unsupported wakumessage version"))
|
||||
}
|
||||
|
||||
keyInfo := &node.KeyInfo{
|
||||
Kind: node.Asymmetric,
|
||||
}
|
||||
|
||||
keyBytes, err := hexutil.Decode(C.GoString(privateKey))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
keyInfo.PrivKey, err = crypto.ToECDSA(keyBytes)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
payload, err := node.DecodePayload(&msg, keyInfo)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
PubKey string `json:"pubkey"`
|
||||
Signature string `json:"signature"`
|
||||
Data []byte `json:"data"`
|
||||
Padding []byte `json:"padding"`
|
||||
}{
|
||||
PubKey: hexutil.Encode(crypto.FromECDSAPub(payload.PubKey)),
|
||||
Signature: hexutil.Encode(payload.Signature),
|
||||
Data: payload.Data,
|
||||
Padding: payload.Padding,
|
||||
}
|
||||
|
||||
return prepareJSONResponse(response, err)
|
||||
response := mobile.DecodeAsymmetric(C.GoString(messageJSON), C.GoString(privateKey))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_utils_base64_decode
|
||||
// Decode a base64 string (useful for reading the payload from waku messages)
|
||||
func waku_utils_base64_decode(data *C.char) *C.char {
|
||||
b, err := base64.StdEncoding.DecodeString(C.GoString(data))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(string(b), nil)
|
||||
}
|
||||
|
||||
//export waku_utils_base64_encode
|
||||
// Encode data to base64 (useful for creating the payload of a waku message in the
|
||||
// format understood by waku_relay_publish)
|
||||
func waku_utils_base64_encode(data *C.char) *C.char {
|
||||
str := base64.StdEncoding.EncodeToString([]byte(C.GoString(data)))
|
||||
return C.CString(string(str))
|
||||
|
||||
}
|
||||
|
||||
//export waku_utils_free
|
||||
// Frees a char* since all strings returned by gowaku are allocated in the C heap using malloc.
|
||||
func waku_utils_free(data *C.char) {
|
||||
C.free(unsafe.Pointer(data))
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// connected/disconnected
|
||||
|
|
|
@ -3,46 +3,8 @@ package main
|
|||
import (
|
||||
"C"
|
||||
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
mobile "github.com/status-im/go-waku/mobile"
|
||||
)
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/lightpush"
|
||||
)
|
||||
|
||||
func lightpushPublish(msg pb.WakuMessage, pubsubTopic string, peerID string, ms int) (string, error) {
|
||||
if wakuNode == nil {
|
||||
return "", ErrWakuNodeNotReady
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
var lpOptions []lightpush.LightPushOption
|
||||
if peerID != "" {
|
||||
p, err := peer.Decode(peerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lpOptions = append(lpOptions, lightpush.WithPeer(p))
|
||||
} else {
|
||||
lpOptions = append(lpOptions, lightpush.WithAutomaticPeerSelection(wakuNode.Host()))
|
||||
}
|
||||
|
||||
hash, err := wakuNode.Lightpush().PublishToTopic(ctx, &msg, pubsubTopic, lpOptions...)
|
||||
return hexutil.Encode(hash), err
|
||||
}
|
||||
|
||||
//export waku_lightpush_publish
|
||||
// Publish a message using waku lightpush. Use NULL for topic to use the default pubsub topic..
|
||||
|
@ -50,13 +12,8 @@ func lightpushPublish(msg pb.WakuMessage, pubsubTopic string, peerID string, ms
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned
|
||||
func waku_lightpush_publish(messageJSON *C.char, topic *C.char, peerID *C.char, ms C.int) *C.char {
|
||||
msg, err := wakuMessage(C.GoString(messageJSON))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := lightpushPublish(msg, getTopic(topic), C.GoString(peerID), int(ms))
|
||||
return prepareJSONResponse(hash, err)
|
||||
response := mobile.LightpushPublish(C.GoString(messageJSON), C.GoString(topic), C.GoString(peerID), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_lightpush_publish_enc_asymmetric
|
||||
|
@ -67,14 +24,8 @@ func waku_lightpush_publish(messageJSON *C.char, topic *C.char, peerID *C.char,
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned.
|
||||
func waku_lightpush_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, peerID *C.char, publicKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
|
||||
msg, err := wakuMessageAsymmetricEncoding(C.GoString(messageJSON), C.GoString(publicKey), C.GoString(optionalSigningKey))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := lightpushPublish(msg, getTopic(topic), C.GoString(peerID), int(ms))
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
response := mobile.LightpushPublishEncodeAsymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(peerID), C.GoString(publicKey), C.GoString(optionalSigningKey), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_lightpush_publish_enc_symmetric
|
||||
|
@ -85,12 +36,6 @@ func waku_lightpush_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, p
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned.
|
||||
func waku_lightpush_publish_enc_symmetric(messageJSON *C.char, topic *C.char, peerID *C.char, symmetricKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
|
||||
msg, err := wakuMessageSymmetricEncoding(C.GoString(messageJSON), C.GoString(symmetricKey), C.GoString(optionalSigningKey))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := lightpushPublish(msg, getTopic(topic), C.GoString(peerID), int(ms))
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
response := mobile.LightpushPublishEncodeSymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(peerID), C.GoString(symmetricKey), C.GoString(optionalSigningKey), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
|
|
@ -2,55 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"C"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
mobile "github.com/status-im/go-waku/mobile"
|
||||
)
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/relay"
|
||||
)
|
||||
|
||||
var subscriptions map[string]*relay.Subscription = make(map[string]*relay.Subscription)
|
||||
var mutex sync.Mutex
|
||||
|
||||
//export waku_relay_enough_peers
|
||||
// Determine if there are enough peers to publish a message on a topic. Use NULL
|
||||
// to verify the number of peers in the default pubsub topic
|
||||
func waku_relay_enough_peers(topic *C.char) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
topicToCheck := protocol.DefaultPubsubTopic().String()
|
||||
if topic != nil {
|
||||
topicToCheck = C.GoString(topic)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(wakuNode.Relay().EnoughPeersToPublishToTopic(topicToCheck), nil)
|
||||
}
|
||||
|
||||
func relayPublish(msg pb.WakuMessage, pubsubTopic string, ms int) (string, error) {
|
||||
if wakuNode == nil {
|
||||
return "", ErrWakuNodeNotReady
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
hash, err := wakuNode.Relay().PublishToTopic(ctx, &msg, pubsubTopic)
|
||||
return hexutil.Encode(hash), err
|
||||
response := mobile.RelayEnoughPeers(C.GoString(topic))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_relay_publish
|
||||
|
@ -58,13 +19,8 @@ func relayPublish(msg pb.WakuMessage, pubsubTopic string, ms int) (string, error
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned
|
||||
func waku_relay_publish(messageJSON *C.char, topic *C.char, ms C.int) *C.char {
|
||||
msg, err := wakuMessage(C.GoString(messageJSON))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := relayPublish(msg, getTopic(topic), int(ms))
|
||||
return prepareJSONResponse(hash, err)
|
||||
response := mobile.RelayPublish(C.GoString(messageJSON), C.GoString(topic), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_relay_publish_enc_asymmetric
|
||||
|
@ -74,14 +30,8 @@ func waku_relay_publish(messageJSON *C.char, topic *C.char, ms C.int) *C.char {
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned.
|
||||
func waku_relay_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, publicKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
|
||||
msg, err := wakuMessageAsymmetricEncoding(C.GoString(messageJSON), C.GoString(publicKey), C.GoString(optionalSigningKey))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := relayPublish(msg, getTopic(topic), int(ms))
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
response := mobile.RelayPublishEncodeAsymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(publicKey), C.GoString(optionalSigningKey), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_relay_publish_enc_symmetric
|
||||
|
@ -91,14 +41,8 @@ func waku_relay_publish_enc_asymmetric(messageJSON *C.char, topic *C.char, publi
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned.
|
||||
func waku_relay_publish_enc_symmetric(messageJSON *C.char, topic *C.char, symmetricKey *C.char, optionalSigningKey *C.char, ms C.int) *C.char {
|
||||
msg, err := wakuMessageSymmetricEncoding(C.GoString(messageJSON), C.GoString(symmetricKey), C.GoString(optionalSigningKey))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := relayPublish(msg, getTopic(topic), int(ms))
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
response := mobile.RelayPublishEncodeSymmetric(C.GoString(messageJSON), C.GoString(topic), C.GoString(symmetricKey), C.GoString(optionalSigningKey), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_relay_subscribe
|
||||
|
@ -107,68 +51,14 @@ func waku_relay_publish_enc_symmetric(messageJSON *C.char, topic *C.char, symmet
|
|||
// or an error message. When a message is received, a "message" is emitted containing
|
||||
// the message, pubsub topic, and nodeID in which the message was received
|
||||
func waku_relay_subscribe(topic *C.char) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
topicToSubscribe := protocol.DefaultPubsubTopic().String()
|
||||
if topic != nil {
|
||||
topicToSubscribe = C.GoString(topic)
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
subscription, ok := subscriptions[topicToSubscribe]
|
||||
if ok {
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
subscription, err := wakuNode.Relay().SubscribeToTopic(context.Background(), topicToSubscribe)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
subscriptions[topicToSubscribe] = subscription
|
||||
|
||||
go func() {
|
||||
for envelope := range subscription.C {
|
||||
send("message", toSubscriptionMessage(envelope))
|
||||
}
|
||||
}()
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
response := mobile.RelaySubscribe(C.GoString(topic))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
||||
//export waku_relay_unsubscribe
|
||||
// Closes the pubsub subscription to a pubsub topic. Existing subscriptions
|
||||
// will not be closed, but they will stop receiving messages
|
||||
func waku_relay_unsubscribe(topic *C.char) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
topicToUnsubscribe := protocol.DefaultPubsubTopic().String()
|
||||
if topic != nil {
|
||||
topicToUnsubscribe = C.GoString(topic)
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
subscription, ok := subscriptions[topicToUnsubscribe]
|
||||
if ok {
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
subscription.Unsubscribe()
|
||||
|
||||
delete(subscriptions, topicToUnsubscribe)
|
||||
|
||||
err := wakuNode.Relay().Unsubscribe(context.Background(), topicToUnsubscribe)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
response := mobile.RelayUnsubscribe(C.GoString(topic))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
|
|
@ -2,37 +2,9 @@ package main
|
|||
|
||||
import (
|
||||
"C"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/store"
|
||||
mobile "github.com/status-im/go-waku/mobile"
|
||||
)
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
type StorePagingOptions struct {
|
||||
PageSize uint64 `json:"pageSize,omitempty"`
|
||||
Cursor *pb.Index `json:"cursor,omitempty"`
|
||||
Forward bool `json:"forward,omitempty"`
|
||||
}
|
||||
|
||||
type StoreMessagesArgs struct {
|
||||
Topic string `json:"pubsubTopic,omitempty"`
|
||||
ContentFilters []string `json:"contentFilters,omitempty"`
|
||||
StartTime int64 `json:"startTime,omitempty"`
|
||||
EndTime int64 `json:"endTime,omitempty"`
|
||||
PagingOptions StorePagingOptions `json:"pagingOptions,omitempty"`
|
||||
}
|
||||
|
||||
type StoreMessagesReply struct {
|
||||
Messages []*pb.WakuMessage `json:"messages,omitempty"`
|
||||
PagingInfo StorePagingOptions `json:"pagingInfo,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
//export waku_store_query
|
||||
// Query historic messages using waku store protocol.
|
||||
|
@ -61,66 +33,6 @@ type StoreMessagesReply struct {
|
|||
// If ms is greater than 0, the broadcast of the message must happen before the timeout
|
||||
// (in milliseconds) is reached, or an error will be returned
|
||||
func waku_store_query(queryJSON *C.char, peerID *C.char, ms C.int) *C.char {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(ErrWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var args StoreMessagesArgs
|
||||
err := json.Unmarshal([]byte(C.GoString(queryJSON)), &args)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
options := []store.HistoryRequestOption{
|
||||
store.WithAutomaticRequestId(),
|
||||
store.WithPaging(args.PagingOptions.Forward, args.PagingOptions.PageSize),
|
||||
store.WithCursor(args.PagingOptions.Cursor),
|
||||
}
|
||||
|
||||
pid := C.GoString(peerID)
|
||||
if pid != "" {
|
||||
p, err := peer.Decode(pid)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
options = append(options, store.WithPeer(p))
|
||||
} else {
|
||||
options = append(options, store.WithAutomaticPeerSelection())
|
||||
}
|
||||
|
||||
reply := StoreMessagesReply{}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
res, err := wakuNode.Store().Query(
|
||||
ctx,
|
||||
store.Query{
|
||||
Topic: args.Topic,
|
||||
ContentTopics: args.ContentFilters,
|
||||
StartTime: args.StartTime,
|
||||
EndTime: args.EndTime,
|
||||
},
|
||||
options...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
reply.Error = err.Error()
|
||||
return prepareJSONResponse(reply, nil)
|
||||
}
|
||||
reply.Messages = res.Messages
|
||||
reply.PagingInfo = StorePagingOptions{
|
||||
PageSize: args.PagingOptions.PageSize,
|
||||
Cursor: res.Cursor(),
|
||||
Forward: args.PagingOptions.Forward,
|
||||
}
|
||||
|
||||
return prepareJSONResponse(reply, nil)
|
||||
response := mobile.StoreQuery(C.GoString(queryJSON), C.GoString(peerID), int(ms))
|
||||
return C.CString(response)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export waku_utils_base64_decode
|
||||
// Decode a base64 string (useful for reading the payload from waku messages)
|
||||
func waku_utils_base64_decode(data *C.char) *C.char {
|
||||
b, err := base64.StdEncoding.DecodeString(C.GoString(data))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(string(b), nil)
|
||||
}
|
||||
|
||||
//export waku_utils_base64_encode
|
||||
// Encode data to base64 (useful for creating the payload of a waku message in the
|
||||
// format understood by waku_relay_publish)
|
||||
func waku_utils_base64_encode(data *C.char) *C.char {
|
||||
str := base64.StdEncoding.EncodeToString([]byte(C.GoString(data)))
|
||||
return C.CString(string(str))
|
||||
|
||||
}
|
||||
|
||||
//export waku_utils_free
|
||||
// Frees a char* since all strings returned by gowaku are allocated in the C heap using malloc.
|
||||
func waku_utils_free(data *C.char) {
|
||||
C.free(unsafe.Pointer(data))
|
||||
}
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
)
|
||||
|
||||
type JSONResponse struct {
|
||||
type jsonResponse struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
@ -14,14 +14,14 @@ func prepareJSONResponse(result interface{}, err error) *C.char {
|
|||
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
errResponse := JSONResponse{
|
||||
errResponse := jsonResponse{
|
||||
Error: &errStr,
|
||||
}
|
||||
response, _ := json.Marshal(&errResponse)
|
||||
return C.CString(string(response))
|
||||
}
|
||||
|
||||
data, err := json.Marshal(JSONResponse{Result: result})
|
||||
data, err := json.Marshal(jsonResponse{Result: result})
|
||||
if err != nil {
|
||||
return prepareJSONResponse(nil, err)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func makeJSONResponse(err error) *C.char {
|
|||
errString = &errStr
|
||||
}
|
||||
|
||||
out := JSONResponse{
|
||||
out := jsonResponse{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(out)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# Mobile
|
||||
|
||||
Package mobile implements [gomobile](https://github.com/golang/mobile) bindings for go-waku.
|
||||
|
||||
## Usage
|
||||
|
||||
For properly using this package, please refer to Makefile in the root of `go-waku` directory.
|
||||
|
||||
To manually build library, run following commands:
|
||||
|
||||
### iOS
|
||||
|
||||
```
|
||||
gomobile init
|
||||
gomobile bind -v -target=ios -ldflags="-s -w" github.com/status-im/go-waku/mobile
|
||||
```
|
||||
This will produce `gowaku.framework` file in the current directory, which can be used in iOS project.
|
||||
|
||||
### Android
|
||||
|
||||
```
|
||||
export ANDROID_NDK_HOME=/path/to/android/ndk
|
||||
export ANDROID_HOME=/path/to/android/sdk/
|
||||
gomobile init
|
||||
gomobile bind -v -target=android -ldflags="-s -w" github.com/status-im/go-waku/mobile
|
||||
```
|
||||
This will generate `gowaku.aar` file in the current dir.
|
||||
|
||||
## Notes
|
||||
|
||||
See [https://github.com/golang/go/wiki/Mobile](https://github.com/golang/go/wiki/Mobile) for more information on `gomobile` usage.
|
|
@ -0,0 +1,404 @@
|
|||
package gowaku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
p2pproto "github.com/libp2p/go-libp2p-core/protocol"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/status-im/go-waku/waku/v2/node"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
)
|
||||
|
||||
var wakuNode *node.WakuNode
|
||||
|
||||
var errWakuNodeNotReady = errors.New("go-waku not initialized")
|
||||
|
||||
func randomHex(n int) (string, error) {
|
||||
bytes := make([]byte, n)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
type wakuConfig struct {
|
||||
Host *string `json:"host,omitempty"`
|
||||
Port *int `json:"port,omitempty"`
|
||||
AdvertiseAddress *string `json:"advertiseAddr,omitempty"`
|
||||
NodeKey *string `json:"nodeKey,omitempty"`
|
||||
KeepAliveInterval *int `json:"keepAliveInterval,omitempty"`
|
||||
EnableRelay *bool `json:"relay"`
|
||||
MinPeersToPublish *int `json:"minPeersToPublish"`
|
||||
}
|
||||
|
||||
var defaultHost = "0.0.0.0"
|
||||
var defaultPort = 60000
|
||||
var defaultKeepAliveInterval = 20
|
||||
var defaultEnableRelay = true
|
||||
var defaultMinPeersToPublish = 0
|
||||
|
||||
func getConfig(configJSON string) (wakuConfig, error) {
|
||||
var config wakuConfig
|
||||
if configJSON != "" {
|
||||
err := json.Unmarshal([]byte(configJSON), &config)
|
||||
if err != nil {
|
||||
return wakuConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.Host == nil {
|
||||
config.Host = &defaultHost
|
||||
}
|
||||
|
||||
if config.EnableRelay == nil {
|
||||
config.EnableRelay = &defaultEnableRelay
|
||||
}
|
||||
|
||||
if config.Host == nil {
|
||||
config.Host = &defaultHost
|
||||
}
|
||||
|
||||
if config.Port == nil {
|
||||
config.Port = &defaultPort
|
||||
}
|
||||
|
||||
if config.KeepAliveInterval == nil {
|
||||
config.KeepAliveInterval = &defaultKeepAliveInterval
|
||||
}
|
||||
|
||||
if config.MinPeersToPublish == nil {
|
||||
config.MinPeersToPublish = &defaultMinPeersToPublish
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func NewNode(configJSON string) string {
|
||||
if wakuNode != nil {
|
||||
return makeJSONResponse(errors.New("go-waku already initialized. stop it first"))
|
||||
}
|
||||
|
||||
config, err := getConfig(configJSON)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hostAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", *config.Host, *config.Port))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
var prvKey *ecdsa.PrivateKey
|
||||
if config.NodeKey != nil {
|
||||
prvKey, err = crypto.HexToECDSA(*config.NodeKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
} else {
|
||||
key, err := randomHex(32)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
prvKey, err = crypto.HexToECDSA(key)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
}
|
||||
|
||||
opts := []node.WakuNodeOption{
|
||||
node.WithPrivateKey(prvKey),
|
||||
node.WithHostAddress(hostAddr),
|
||||
node.WithKeepAlive(time.Duration(*config.KeepAliveInterval) * time.Second),
|
||||
}
|
||||
|
||||
if *config.EnableRelay {
|
||||
opts = append(opts, node.WithWakuRelayAndMinPeers(*config.MinPeersToPublish))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
w, err := node.New(ctx, opts...)
|
||||
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
wakuNode = w
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
func Start() string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
if err := wakuNode.Start(); err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
func Stop() string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
wakuNode.Stop()
|
||||
wakuNode = nil
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
func PeerID() string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(wakuNode.ID(), nil)
|
||||
}
|
||||
|
||||
func ListenAddresses() string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var addresses []string
|
||||
for _, addr := range wakuNode.ListenAddresses() {
|
||||
addresses = append(addresses, addr.String())
|
||||
}
|
||||
|
||||
return prepareJSONResponse(addresses, nil)
|
||||
}
|
||||
|
||||
func AddPeer(address string, protocolID string) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
ma, err := multiaddr.NewMultiaddr(address)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
peerID, err := wakuNode.AddPeer(ma, p2pproto.ID(protocolID))
|
||||
return prepareJSONResponse(peerID, err)
|
||||
}
|
||||
|
||||
func Connect(address string, ms int) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
err := wakuNode.DialPeer(ctx, address)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
func ConnectPeerID(peerID string, ms int) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
pID, err := peer.Decode(peerID)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
err = wakuNode.DialPeerByID(ctx, pID)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
func Disconnect(peerID string) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
pID, err := peer.Decode(peerID)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
err = wakuNode.ClosePeerById(pID)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
func PeerCnt() string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
return prepareJSONResponse(wakuNode.PeerCount(), nil)
|
||||
}
|
||||
|
||||
func ContentTopic(applicationName string, applicationVersion int, contentTopicName string, encoding string) string {
|
||||
return protocol.NewContentTopic(applicationName, uint(applicationVersion), contentTopicName, encoding).String()
|
||||
}
|
||||
|
||||
func PubsubTopic(name string, encoding string) string {
|
||||
return protocol.NewPubsubTopic(name, encoding).String()
|
||||
}
|
||||
|
||||
func DefaultPubsubTopic() string {
|
||||
return protocol.DefaultPubsubTopic().String()
|
||||
}
|
||||
|
||||
func getTopic(topic string) string {
|
||||
if topic == "" {
|
||||
return protocol.DefaultPubsubTopic().String()
|
||||
}
|
||||
return topic
|
||||
}
|
||||
|
||||
type subscriptionMsg struct {
|
||||
MessageID string `json:"messageID"`
|
||||
PubsubTopic string `json:"pubsubTopic"`
|
||||
Message *pb.WakuMessage `json:"wakuMessage"`
|
||||
}
|
||||
|
||||
func toSubscriptionMessage(msg *protocol.Envelope) *subscriptionMsg {
|
||||
return &subscriptionMsg{
|
||||
MessageID: hexutil.Encode(msg.Hash()),
|
||||
PubsubTopic: msg.PubsubTopic(),
|
||||
Message: msg.Message(),
|
||||
}
|
||||
}
|
||||
|
||||
func Peers() string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
peers, err := wakuNode.Peers()
|
||||
return prepareJSONResponse(peers, err)
|
||||
}
|
||||
|
||||
func unmarshalPubkey(pub []byte) (ecdsa.PublicKey, error) {
|
||||
x, y := elliptic.Unmarshal(secp256k1.S256(), pub)
|
||||
if x == nil {
|
||||
return ecdsa.PublicKey{}, errors.New("invalid public key")
|
||||
}
|
||||
return ecdsa.PublicKey{Curve: secp256k1.S256(), X: x, Y: y}, nil
|
||||
}
|
||||
|
||||
func DecodeSymmetric(messageJSON string, symmetricKey string) string {
|
||||
var msg pb.WakuMessage
|
||||
err := json.Unmarshal([]byte(messageJSON), &msg)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if msg.Version == 0 {
|
||||
return prepareJSONResponse(msg.Payload, nil)
|
||||
} else if msg.Version > 1 {
|
||||
return makeJSONResponse(errors.New("unsupported wakumessage version"))
|
||||
}
|
||||
|
||||
keyInfo := &node.KeyInfo{
|
||||
Kind: node.Symmetric,
|
||||
}
|
||||
|
||||
keyInfo.SymKey, err = hexutil.Decode(symmetricKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
payload, err := node.DecodePayload(&msg, keyInfo)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
PubKey string `json:"pubkey"`
|
||||
Signature string `json:"signature"`
|
||||
Data []byte `json:"data"`
|
||||
Padding []byte `json:"padding"`
|
||||
}{
|
||||
PubKey: hexutil.Encode(crypto.FromECDSAPub(payload.PubKey)),
|
||||
Signature: hexutil.Encode(payload.Signature),
|
||||
Data: payload.Data,
|
||||
Padding: payload.Padding,
|
||||
}
|
||||
|
||||
return prepareJSONResponse(response, err)
|
||||
}
|
||||
|
||||
func DecodeAsymmetric(messageJSON string, privateKey string) string {
|
||||
var msg pb.WakuMessage
|
||||
err := json.Unmarshal([]byte(messageJSON), &msg)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if msg.Version == 0 {
|
||||
return prepareJSONResponse(msg.Payload, nil)
|
||||
} else if msg.Version > 1 {
|
||||
return makeJSONResponse(errors.New("unsupported wakumessage version"))
|
||||
}
|
||||
|
||||
keyInfo := &node.KeyInfo{
|
||||
Kind: node.Asymmetric,
|
||||
}
|
||||
|
||||
keyBytes, err := hexutil.Decode(privateKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
keyInfo.PrivKey, err = crypto.ToECDSA(keyBytes)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
payload, err := node.DecodePayload(&msg, keyInfo)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
PubKey string `json:"pubkey"`
|
||||
Signature string `json:"signature"`
|
||||
Data []byte `json:"data"`
|
||||
Padding []byte `json:"padding"`
|
||||
}{
|
||||
PubKey: hexutil.Encode(crypto.FromECDSAPub(payload.PubKey)),
|
||||
Signature: hexutil.Encode(payload.Signature),
|
||||
Data: payload.Data,
|
||||
Padding: payload.Padding,
|
||||
}
|
||||
|
||||
return prepareJSONResponse(response, err)
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package gowaku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/lightpush"
|
||||
)
|
||||
|
||||
func lightpushPublish(msg pb.WakuMessage, pubsubTopic string, peerID string, ms int) (string, error) {
|
||||
if wakuNode == nil {
|
||||
return "", errWakuNodeNotReady
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
var lpOptions []lightpush.LightPushOption
|
||||
if peerID != "" {
|
||||
p, err := peer.Decode(peerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lpOptions = append(lpOptions, lightpush.WithPeer(p))
|
||||
} else {
|
||||
lpOptions = append(lpOptions, lightpush.WithAutomaticPeerSelection(wakuNode.Host()))
|
||||
}
|
||||
|
||||
hash, err := wakuNode.Lightpush().PublishToTopic(ctx, &msg, pubsubTopic, lpOptions...)
|
||||
return hexutil.Encode(hash), err
|
||||
}
|
||||
|
||||
func LightpushPublish(messageJSON string, topic string, peerID string, ms int) string {
|
||||
msg, err := wakuMessage(messageJSON)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := lightpushPublish(msg, getTopic(topic), peerID, ms)
|
||||
return prepareJSONResponse(hash, err)
|
||||
}
|
||||
|
||||
func LightpushPublishEncodeAsymmetric(messageJSON string, topic string, peerID string, publicKey string, optionalSigningKey string, ms int) string {
|
||||
msg, err := wakuMessageAsymmetricEncoding(messageJSON, publicKey, optionalSigningKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := lightpushPublish(msg, getTopic(topic), peerID, ms)
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
}
|
||||
|
||||
func LightpushPublishEncodeSymmetric(messageJSON string, topic string, peerID string, symmetricKey string, optionalSigningKey string, ms int) string {
|
||||
msg, err := wakuMessageSymmetricEncoding(messageJSON, symmetricKey, optionalSigningKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := lightpushPublish(msg, getTopic(topic), peerID, ms)
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package gowaku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/relay"
|
||||
)
|
||||
|
||||
var subscriptions map[string]*relay.Subscription = make(map[string]*relay.Subscription)
|
||||
var mutex sync.Mutex
|
||||
|
||||
func RelayEnoughPeers(topic string) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
topicToCheck := protocol.DefaultPubsubTopic().String()
|
||||
if topic != "" {
|
||||
topicToCheck = topic
|
||||
}
|
||||
|
||||
return prepareJSONResponse(wakuNode.Relay().EnoughPeersToPublishToTopic(topicToCheck), nil)
|
||||
}
|
||||
|
||||
func relayPublish(msg pb.WakuMessage, pubsubTopic string, ms int) (string, error) {
|
||||
if wakuNode == nil {
|
||||
return "", errWakuNodeNotReady
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
hash, err := wakuNode.Relay().PublishToTopic(ctx, &msg, pubsubTopic)
|
||||
return hexutil.Encode(hash), err
|
||||
}
|
||||
|
||||
func RelayPublish(messageJSON string, topic string, ms int) string {
|
||||
msg, err := wakuMessage(messageJSON)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := relayPublish(msg, getTopic(topic), int(ms))
|
||||
return prepareJSONResponse(hash, err)
|
||||
}
|
||||
|
||||
func RelayPublishEncodeAsymmetric(messageJSON string, topic string, publicKey string, optionalSigningKey string, ms int) string {
|
||||
msg, err := wakuMessageAsymmetricEncoding(messageJSON, publicKey, optionalSigningKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := relayPublish(msg, getTopic(topic), int(ms))
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
}
|
||||
|
||||
func RelayPublishEncodeSymmetric(messageJSON string, topic string, symmetricKey string, optionalSigningKey string, ms int) string {
|
||||
msg, err := wakuMessageSymmetricEncoding(messageJSON, symmetricKey, optionalSigningKey)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
hash, err := relayPublish(msg, getTopic(topic), int(ms))
|
||||
|
||||
return prepareJSONResponse(hash, err)
|
||||
}
|
||||
|
||||
func RelaySubscribe(topic string) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
topicToSubscribe := getTopic(topic)
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
subscription, ok := subscriptions[topicToSubscribe]
|
||||
if ok {
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
subscription, err := wakuNode.Relay().SubscribeToTopic(context.Background(), topicToSubscribe)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
subscriptions[topicToSubscribe] = subscription
|
||||
|
||||
go func() {
|
||||
for envelope := range subscription.C {
|
||||
send("message", toSubscriptionMessage(envelope))
|
||||
}
|
||||
}()
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
func RelayUnsubscribe(topic string) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
topicToUnsubscribe := getTopic(topic)
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
subscription, ok := subscriptions[topicToUnsubscribe]
|
||||
if ok {
|
||||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
subscription.Unsubscribe()
|
||||
|
||||
delete(subscriptions, topicToUnsubscribe)
|
||||
|
||||
err := wakuNode.Relay().Unsubscribe(context.Background(), topicToUnsubscribe)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return makeJSONResponse(nil)
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package gowaku
|
||||
|
||||
import (
|
||||
"C"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||
"github.com/status-im/go-waku/waku/v2/protocol/store"
|
||||
)
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
type storePagingOptions struct {
|
||||
PageSize uint64 `json:"pageSize,omitempty"`
|
||||
Cursor *pb.Index `json:"cursor,omitempty"`
|
||||
Forward bool `json:"forward,omitempty"`
|
||||
}
|
||||
|
||||
type storeMessagesArgs struct {
|
||||
Topic string `json:"pubsubTopic,omitempty"`
|
||||
ContentFilters []string `json:"contentFilters,omitempty"`
|
||||
StartTime int64 `json:"startTime,omitempty"`
|
||||
EndTime int64 `json:"endTime,omitempty"`
|
||||
PagingOptions storePagingOptions `json:"pagingOptions,omitempty"`
|
||||
}
|
||||
|
||||
type storeMessagesReply struct {
|
||||
Messages []*pb.WakuMessage `json:"messages,omitempty"`
|
||||
PagingInfo storePagingOptions `json:"pagingInfo,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func StoreQuery(queryJSON string, peerID string, ms int) string {
|
||||
if wakuNode == nil {
|
||||
return makeJSONResponse(errWakuNodeNotReady)
|
||||
}
|
||||
|
||||
var args storeMessagesArgs
|
||||
err := json.Unmarshal([]byte(queryJSON), &args)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
options := []store.HistoryRequestOption{
|
||||
store.WithAutomaticRequestId(),
|
||||
store.WithPaging(args.PagingOptions.Forward, args.PagingOptions.PageSize),
|
||||
store.WithCursor(args.PagingOptions.Cursor),
|
||||
}
|
||||
|
||||
if peerID != "" {
|
||||
p, err := peer.Decode(peerID)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
options = append(options, store.WithPeer(p))
|
||||
} else {
|
||||
options = append(options, store.WithAutomaticPeerSelection())
|
||||
}
|
||||
|
||||
reply := storeMessagesReply{}
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
if ms > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(int(ms))*time.Millisecond)
|
||||
defer cancel()
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
res, err := wakuNode.Store().Query(
|
||||
ctx,
|
||||
store.Query{
|
||||
Topic: args.Topic,
|
||||
ContentTopics: args.ContentFilters,
|
||||
StartTime: args.StartTime,
|
||||
EndTime: args.EndTime,
|
||||
},
|
||||
options...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
reply.Error = err.Error()
|
||||
return prepareJSONResponse(reply, nil)
|
||||
}
|
||||
reply.Messages = res.Messages
|
||||
reply.PagingInfo = storePagingOptions{
|
||||
PageSize: args.PagingOptions.PageSize,
|
||||
Cursor: res.Cursor(),
|
||||
Forward: args.PagingOptions.Forward,
|
||||
}
|
||||
|
||||
return prepareJSONResponse(reply, nil)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package gowaku
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,6 +1,7 @@
|
|||
//go:build darwin && cgo
|
||||
// +build darwin,cgo
|
||||
|
||||
package main
|
||||
package gowaku
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
|
@ -0,0 +1,41 @@
|
|||
package gowaku
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type jsonResponse struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
func prepareJSONResponse(result interface{}, err error) string {
|
||||
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
errResponse := jsonResponse{
|
||||
Error: &errStr,
|
||||
}
|
||||
response, _ := json.Marshal(&errResponse)
|
||||
return string(response)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(jsonResponse{Result: result})
|
||||
if err != nil {
|
||||
return prepareJSONResponse(nil, err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func makeJSONResponse(err error) string {
|
||||
var errString *string = nil
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
errString = &errStr
|
||||
}
|
||||
|
||||
out := jsonResponse{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(out)
|
||||
|
||||
return string(outBytes)
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
package main
|
||||
package gowaku
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern bool StatusServiceSignalEvent(const char *jsonEvent);
|
||||
extern void SetEventCallback(void *cb);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -27,15 +28,15 @@ type MobileSignalHandler func([]byte)
|
|||
// storing the current mobile signal handler here
|
||||
var mobileSignalHandler MobileSignalHandler
|
||||
|
||||
// SignalEnvelope is a general signal sent upward from node to app
|
||||
type SignalEnvelope struct {
|
||||
// signalEnvelope is a general signal sent upward from node to app
|
||||
type signalEnvelope struct {
|
||||
Type string `json:"type"`
|
||||
Event interface{} `json:"event"`
|
||||
}
|
||||
|
||||
// NewEnvelope creates new envlope of given type and event payload.
|
||||
func NewEnvelope(signalType string, event interface{}) *SignalEnvelope {
|
||||
return &SignalEnvelope{
|
||||
func newEnvelope(signalType string, event interface{}) *signalEnvelope {
|
||||
return &signalEnvelope{
|
||||
Type: signalType,
|
||||
Event: event,
|
||||
}
|
||||
|
@ -43,7 +44,8 @@ func NewEnvelope(signalType string, event interface{}) *SignalEnvelope {
|
|||
|
||||
// send sends application signal (in JSON) upwards to application (via default notification handler)
|
||||
func send(signalType string, event interface{}) {
|
||||
signal := NewEnvelope(signalType, event)
|
||||
|
||||
signal := newEnvelope(signalType, event)
|
||||
data, err := json.Marshal(&signal)
|
||||
if err != nil {
|
||||
fmt.Println("marshal signal error", err)
|
||||
|
@ -71,6 +73,6 @@ func SetMobileSignalHandler(handler SignalHandler) {
|
|||
}
|
||||
}
|
||||
|
||||
func setEventCallback(cb unsafe.Pointer) {
|
||||
func SetEventCallback(cb unsafe.Pointer) {
|
||||
C.SetEventCallback(cb)
|
||||
}
|