Skip to content

Commit

Permalink
WIP: expo module
Browse files Browse the repository at this point in the history
  • Loading branch information
b5 committed Jun 5, 2024
1 parent e3108e4 commit 2ef2ea3
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 0 deletions.
43 changes: 43 additions & 0 deletions expo/iroh-expo/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
apply plugin: 'com.android.library'

group = 'expo.modules.irohexpo'
version = '0.6.0'

def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useExpoPublishing()

// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
// Most of the time, you may like to manage the Android SDK versions yourself.
def useManagedAndroidSdkVersions = false
if (useManagedAndroidSdkVersions) {
useDefaultAndroidSdkVersions()
} else {
buildscript {
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
}
project.android {
compileSdkVersion safeExtGet("compileSdkVersion", 34)
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 34)
}
}
}

android {
namespace "expo.modules.irohexpo"
defaultConfig {
versionCode 1
versionName "0.6.0"
}
lintOptions {
abortOnError false
}
}
2 changes: 2 additions & 0 deletions expo/iroh-expo/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package expo.modules.irohexpo

import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition

class IrohExpoModule : Module() {
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
override fun definition() = ModuleDefinition {
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
// The module will be accessible from `requireNativeModule('IrohExpo')` in JavaScript.
Name("IrohExpo")

// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
Constants(
"PI" to Math.PI
)

// Defines event names that the module can send to JavaScript.
Events("onChange")

// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
Function("hello") {
"Hello world! 👋"
}

// Defines a JavaScript function that always returns a Promise and whose native code
// is by default dispatched on the different thread than the JavaScript runtime runs on.
AsyncFunction("setValueAsync") { value: String ->
// Send an event to JavaScript.
sendEvent("onChange", mapOf(
"value" to value
))
}

// Enables the module to be used as a native view. Definition components that are accepted as part of
// the view definition: Prop, Events.
View(IrohExpoView::class) {
// Defines a setter for the `name` prop.
Prop("name") { view: IrohExpoView, prop: String ->
println(prop)
}
}
}
}
9 changes: 9 additions & 0 deletions expo/iroh-expo/expo-module.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"platforms": ["ios", "tvos", "android", "web"],
"ios": {
"modules": ["IrohExpoModule"]
},
"android": {
"modules": ["expo.modules.irohexpo.IrohExpoModule"]
}
}
35 changes: 35 additions & 0 deletions expo/iroh-expo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Import the native module. On web, it will be resolved to IrohExpo.web.ts
// and on native platforms to IrohExpo.ts
import IrohExpoModule from './src/IrohExpoModule';
import { AddrInfoOptions, ChangeEventPayload, ShareMode } from './src/IrohExpo.types';

export async function nodeId(): Promise<string> {
return IrohExpoModule.nodeId();
}

export async function docCreate(): Promise<Doc> {
let id = await IrohExpoModule.docCreate();
return new Doc(id);
}

export async function docDrop(id: string) {
return await IrohExpoModule.docDrop(id);
}

export async function docJoin(ticket: string): Promise<Doc> {
let id = await IrohExpoModule.docJoin(ticket);
return new Doc(id);
}


export class Doc {
public id: string;

constructor(id: string) {
this.id = id;
}

async share(mode: ShareMode, addrOptions: AddrInfoOptions) {
return IrohExpoModule.docShare(this.id, mode, addrOptions);
}
}
68 changes: 68 additions & 0 deletions expo/iroh-expo/ios/Documents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import ExpoModulesCore
import IrohLib

/**
* Options when creating a ticket
*/
public enum AddrInfoOptionsString: String, Enumerable {
/**
* Only the Node ID is added.
*
* This usually means that iroh-dns discovery is used to find address information.
*/
case id
/**
* Include both the relay URL and the direct addresses.
*/
case relayAndAddresses
/**
* Only include the relay URL.
*/
case relay
/**
* Only include the direct addresses.
*/
case addresses
}

// convert to AddrInfoOptions
extension AddrInfoOptionsString {
func toAddrInfoOptions() -> AddrInfoOptions {
switch self {
case .id:
return AddrInfoOptions.id
case .addresses:
return AddrInfoOptions.addresses
case .relay:
return AddrInfoOptions.relay
case .relayAndAddresses:
return AddrInfoOptions.relayAndAddresses
}
}
}

/**
* Intended capability for document share tickets
*/
public enum ShareModeString: String, Enumerable {
/**
* Read-only access
*/
case read
/**
* Write access
*/
case write
}

// convert to ShareMode
extension ShareModeString {
func toShareMode() -> ShareMode {
switch self {
case .read:
return ShareMode.read
case .write:
return ShareMode.write
}
}
}
22 changes: 22 additions & 0 deletions expo/iroh-expo/ios/IrohExpo.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Pod::Spec.new do |s|
s.name = 'IrohExpo'
s.version = '1.0.0'
s.summary = 'A sample project summary'
s.description = 'A sample project description'
s.author = ''
s.homepage = 'https://docs.expo.dev/modules/'
s.platforms = { :ios => '15.4', :tvos => '15.4' }
s.source = { git: '' }
s.static_framework = true

s.dependency 'ExpoModulesCore'
s.dependency 'IrohLib'

# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}

s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
end
128 changes: 128 additions & 0 deletions expo/iroh-expo/ios/IrohExpoModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import ExpoModulesCore
import IrohLib

public class IrohExpoModule: Module {
private var node: IrohNode?

private func irohPath() -> URL {
let paths = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)
let irohPath = paths[0].appendingPathComponent("iroh")
mkdirP(path: irohPath.path)
return irohPath
}

public func definition() -> ModuleDefinition {
Name("IrohExpo")

OnCreate {
do {
// IrohLib.setLogLevel(level: .debug)
// try IrohLib.startMetricsCollection()
let path = self.irohPath()
self.node = try IrohNode(path: path.path)
} catch {
print("error creating iroh node \(error)")
}
}

AsyncFunction("nodeId") {
return self.node?.nodeId()
}

AsyncFunction("docCreate") {
guard let doc = try self.node?.docCreate() else {
throw IrohError.Doc(description: "error creating doc")
}
return doc.id();
}

AsyncFunction("docDrop") { (docId: String) in
return try self.node?.docDrop(id: docId)
}

AsyncFunction("docOpen") { (docId: String) in
return try self.node?.docOpen(id: docId)
}

AsyncFunction("docJoin") { (ticket: String) in
return try self.node?.docJoin(ticket: ticket)
}

AsyncFunction("docShare") { (docId: String, mode: ShareModeString, addrOptions: AddrInfoOptionsString) in
guard let doc = try self.node?.docOpen(id: docId) else {
throw IrohError.Doc(description: "error opening doc")
}
return try doc.share(mode: mode.toShareMode(), addrOptions: addrOptions.toAddrInfoOptions())
}

AsyncFunction("docSetBytes") { (docId: string, author: AuthorId, key: Data, bytes: Data) in
guard let doc = try self.node?.docOpen(id: docId) else {
throw IrohError.Doc(description: "error opening doc")
}

return try doc.setBytes(author: author, key: key, bytes: bytes)
}
}
}

func mkdirP(path: String) {
let fileManager = FileManager.default
do {
try fileManager.createDirectory(atPath: path,
withIntermediateDirectories: true,
attributes: nil)
} catch {
print("Error creating directory: \(error)")
}
}



/// A representation of a mutable, synchronizable key-value store.
interface Doc {
/// Get the document id of this doc.
string id();
/// Close the document.
void close();
/// Set the content of a key to a byte array.
Hash set_bytes([ByRef] AuthorId author, bytes key, bytes value);
/// Set an entries on the doc via its key, hash, and size.
void set_hash(AuthorId author, bytes key, Hash hash, u64 size);
/// Add an entry from an absolute file path
void import_file(AuthorId author, bytes key, string path, boolean in_place, DocImportFileCallback? cb);
/// Export an entry as a file to a given absolute path
void export_file(Entry entry, string path, DocExportFileCallback? cb);
/// Delete entries that match the given `author` and key `prefix`.
///
/// This inserts an empty entry with the key set to `prefix`, effectively clearing all other
/// entries whose key starts with or is equal to the given `prefix`.
///
/// Returns the number of entries deleted.
u64 del(AuthorId author_id, bytes prefix);
/// Get the latest entry for a key and author.
Entry? get_one(Query query);
/// Get entries.
///
/// Note: this allocates for each `Entry`, if you have many `Entry`s this may be a prohibitively large list.
/// Please file an [issue](https://github.com/n0-computer/iroh-ffi/issues/new) if you run into this issue
sequence<Entry> get_many(Query query);
/// Get an entry for a key and author.
///
/// Optionally also get the entry if it is empty (i.e. a deletion marker)
Entry? get_exact(AuthorId author, bytes key, boolean include_empty);

/// Share this document with peers over a ticket.
string share(ShareMode mode, AddrInfoOptions addr_options);
/// Start to sync this document with a list of peers.
void start_sync(sequence<NodeAddr> peers);
/// Stop the live sync for this document.
void leave();
/// Subscribe to events for this document.
void subscribe(SubscribeCallback cb);
/// Get status info for this document
OpenState status();
/// Set the download policy for this document
void set_download_policy(DownloadPolicy policy);
/// Get the download policy for this document
DownloadPolicy get_download_policy();
};
34 changes: 34 additions & 0 deletions expo/iroh-expo/src/IrohExpo.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export type ChangeEventPayload = {
value: string;
};

/// Options passed to [`IrohNode.new`]. Controls the behaviour of an iroh node.
export type NodeOptions = {
/// How frequently the blob store should clean up unreferenced blobs, in milliseconds.
/// Set to 0 to disable gc
GCIntervalMillis: number
};


/// Intended capability for document share tickets
export enum ShareMode {
/// Read-only access
read = "read",
/// Write access
write = "write",
};


/// Options when creating a ticket
export enum AddrInfoOptions {
/// Only the Node ID is added.
///
/// This usually means that iroh-dns discovery is used to find address information.
id = "id",
/// Include both the relay URL and the direct addresses.
relayAndAddresses = "relayAndAddresses",
/// Only include the relay URL.
relay = "relay",
/// Only include the direct addresses.
addresses = "addresses",
};
Loading

0 comments on commit 2ef2ea3

Please sign in to comment.