core:
  setup:
    js_http_client:
      code: |
        // No initialization needed, only import
        import textile from "@textile/js-http-client"
    react_native:
      code: |
        import textile from "@textile/react-native-sdk"
        // Make the sensitive below phrase available to your user for secure storage
        const phrase: string | undefined = await textile.initialize(false, false)
    objc:
      code: |
        // Early on in the app lifecycle, usually in AppDelegate
        // Initialize a new Textile wallet for the user.
        NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *repoPath = [documents stringByAppendingPathComponent:@"textile-repo"];

        if (![Textile isInitialized:repoPath]) {
          NSError *error;
          NSString *recoveryPhrase = [Textile initializeCreatingNewWalletAndAccount:repoPath debug:NO logToDisk:NO error:&error];
          if (!recoveryPhrase) {
            NSLog(@"init error: %@", error.localizedDescription);
          }
          // Return phrase to the user for secure, out of app, storage
        }

        NSError *error;
        BOOL launched = [Textile launch:repoPath debug:NO error:&error];
        if (!launched) {
          NSLog(@"launch error: %@", error.localizedDescription);
        }

        // Set the Textile delegate to any object that conforms to the TextileDelegate protocol
        Textile.instance.delegate = self;
    swift:
      code: |
        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let repoPath = (documentsPath as NSString).appendingPathComponent("textile-repo")

        if(!Textile.isInitialized(repoPath)) {
          var error: NSError?
          let recoveryPhrase = Textile.initializeCreatingNewWalletAndAccount(repoPath, debug: false, logToDisk: false, error: &error)
          // Return phrase to the user for secure, out of app, storage
        }

        do {
          try Textile.launch(repoPath, debug: false)
        } catch {
          // handle launch error
        }

        // Set the Textile delegate to self so we can make use of events such nodeStarted
        Textile.instance().delegate = self
    android:
      code: |
        try {
            Context ctx = getApplicationContext();

            final File filesDir = ctx.getFilesDir();
            final String path = new File(filesDir, "textile-repo").getAbsolutePath();

            if (!Textile.isInitialized(path)) {
                String phrase = Textile.initializeCreatingNewWalletAndAccount(path, true, false);
                System.out.println(phrase);
                // Return phrase to the user for secure, out of app, storage
            }

            Textile.launch(ctx, path, true);

            class MyEventListener extends BaseTextileEventListener {
                // ...
            }

            Textile.instance().addEventListener(new MyEventListener());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
  daemon:
    cmd:
      code: 'textile daemon --repo="/tmp/buddy"'
  logs:
    cmd:
      code: 'textile logs'
    js_http_client:
      code: 'const levels = await textile.logs.get()'
  logs_text_only:
    cmd:
      code: 'textile logs --level="info" --tex-only'
    js_http_client:
      code: 'const levels = await textile.logs.set("info", undefined, true)'
  init:
    cmd:
      code: 'textile init "$(textile wallet init | tail -n1)" --repo="/tmp/buddy" --swarm-ports="4101" --api-bind-addr="127.0.0.1:41600" --gateway-bind-addr="127.0.0.1:5150 --profile-bind-addr="127.0.0.1:6061""'
    react_native:
      code: |
        // This is the user's new Textile wallet seed. They must keep it secure and never share it. Your production app should not keep a copy long term and should never share it with any API.
        const seed = await textile.initialize(false, false)
        alert(`WARNING - Never lose or share your seed: ${seed}`)
    objc:
      code: |
        // Early on in the app lifecycle, usually in AppDelegate
        // Initialize a new Textile wallet for the user.
        NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *repoPath = [documents stringByAppendingPathComponent:@"textile-repo"];

        if (![Textile isInitialized:repoPath]) {
          NSError *error;
          NSString *recoveryPhrase = [Textile initializeCreatingNewWalletAndAccount:repoPath debug:NO logToDisk:NO error:&error];
          if (!recoveryPhrase) {
            NSLog(@"init error: %@", error.localizedDescription);
          }
          // Return phrase to the user for secure, out of app, storage
        }

        NSError *error;
        BOOL launched = [Textile launch:repoPath debug:NO error:&error];
        if (!launched) {
          NSLog(@"launch error: %@", error.localizedDescription);
        }

        // Set the Textile delegate to any object that conforms to the TextileDelegate protocol
        Textile.instance.delegate = self;
    swift:
      code: |
        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let repoPath = (documentsPath as NSString).appendingPathComponent("textile-repo")

        if(!Textile.isInitialized(repoPath)) {
          var error: NSError?
          let recoveryPhrase = Textile.initializeCreatingNewWalletAndAccount(repoPath, debug: false, logToDisk: false, error: &error)
          // Return phrase to the user for secure, out of app, storage
        }

        do {
          try Textile.launch(repoPath, debug: false)
        } catch {
          // handle launch error
        }

        // Set the Textile delegate to self so we can make use of events such nodeStarted
        Textile.instance().delegate = self
    android:
      code: |
        try {
            Context ctx = getApplicationContext();

            final File filesDir = ctx.getFilesDir();
            final String path = new File(filesDir, "textile-repo").getAbsolutePath();

            if (!Textile.isInitialized(path)) {
                String phrase = Textile.initializeCreatingNewWalletAndAccount(path, true, false);
                System.out.println(phrase);
                // Return phrase to the user for secure, out of app, storage
            }

            Textile.launch(ctx, path, true);

            class MyEventListener extends BaseTextileEventListener {
                // ...
            }

            Textile.instance().addEventListener(new MyEventListener());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
  reinit:
    react_native:
      code: '@todo'
  pair:
    react_native:
      code: '@todo'
  summary:
    cmd:
      code: 'textile summary'
    js_http_client:
      code: 'const summary = await textile.utils.summary()'
    react_native:
      code: 'const summary = await textile.summary()'
    objc:
      code: |
        NSError *error;
        Summary *summary = [Textile.instance summary:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let summary = Textile.instance().summary(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Summary summary = Textile.instance().summary();'
  ping:
    cmd:
      code: 'textile ping 12D3KooWLh9Gd4C3knv4XqCyCuaNddfEoSLXgekVJzRyC5vsjv5d'
    js_http_client:
      code: 'await textile.utils.ping("12D3KooWLh9Gd4C3knv4XqCyCuaNddfEoSLXgekVJzRyC5vsjv5d")'
    react_native:
      code: '@todo'
    objc:
      code: '@todo'
    swift:
      code: '@todo'
    android:
      code: '@todo'
  config:
    cmd:
      code: 'textile config "Addresses"'
    js_http_client:
      code: 'const config = await textile.config.get("Addresses")'
  config_set:
    cmd:
      code: 'textile config "Addresses.Gateway" \"127.0.0.1:9090\"'
    js_http_client:
      code: 'const config = await textile.config.set("Addresses.Gateway", "127.0.0.1:9090")'
subscribe:
  files:
    cmd:
      code: 'textile subscribe --type="files"'
    js_http_client:
      code: |
        // The js-http-client returns a `ReadableStream` to be accessed by the caller
        // See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream for details
        const stream = await textile.subscribe.stream("files")
        const reader = stream.getReader()
        const read = (result) => { // ReadableStreamReadResult<FeedItem>
          if (result.done) {
            return
          }
          try {
            console.log(result.value)
          } catch (err) {
            reader.cancel(undefined)
            return
          }
          read(await reader.read())
        }
        read(await reader.read())
    react_native:
      code: |
        textile.events.addThreadUpdateReceivedListener((update) => {
          const { type_url } = update.payload
          if (type_url === '/Files') {
            // new file!
          }
        })
    objc:
      code: |
        // Set the Textile delegate to any object that conforms to the TextileDelegate protocol
        Textile.instance.delegate = delegate;

        // The delegate can receive callbacks for any event it is interested in, for example
        - (void)threadUpdateReceived:(FeedItem *)feedItem {
          if ([feedItem.payload.typeURL isEqualToString:@"/Files"]) {
            // new file!
          }
        }
    swift:
      code: |
        // Set the Textile delegate to any object that conforms to the TextileDelegate protocol
        Textile.instance().delegate = delegate

        // The delegate can receive callbacks for any event it is interested in, for example
        func threadUpdateReceived(_ feedItem: FeedItem) {
          if (feedItem.payload.typeURL == "/Files") {
            // new file!
          }
        }
    android:
      code: |
        // Add an event listener that conforms to TextileEventListener
        class MyEventListener extends BaseTextileEventListener {
            @Override
            public void threadUpdateReceived(io.textile.pb.View.FeedItem feedItem) {
                if (feedItem.getPayload().getTypeUrl() == "/Files") {
                    // new file!
                }
            }
        }
        Textile.instance().addEventListener(new MyEventListener());
notifications:
  ls:
    cmd:
      code: 'textile notifications ls'
    js_http_client:
      code: 'await textile.notifications.list()'
    react_native:
      code: 'const notifications = await textile.notifications.list()'
    objc:
      code: |
        NSError *error;
        NotificationList *notifications = [Textile.instance.notifications list:@"0" limit:10 error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let notifications = Textile.instance().notifications.list(nil, limit: 10, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'NotificationList notifications = Textile.instance().notifications.list("", 10);'
  read:
    cmd:
      code: 'textile notifications read "1KKP01K9SpRASYKkyxSK4EiSYEz"'
    js_http_client:
      code: 'await textile.notifications.read("1KKP01K9SpRASYKkyxSK4EiSYEz")'
    react_native:
      code: 'await textile.notifications.read("1KKP01K9SpRASYKkyxSK4EiSYEz")'
    objc:
      code: |
        NSError *error;
        [Textile.instance.notifications read:@"1KKP01K9SpRASYKkyxSK4EiSYEz" error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        Textile.instance().notifications.read("1KKP01K9SpRASYKkyxSK4EiSYEz", error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Textile.instance().notifications.read("1KKP01K9SpRASYKkyxSK4EiSYEz");'
profile:
  get_:
    cmd:
      code: 'textile profile get'
    js_http_client:
      code: 'const profile = await textile.profile.get()'
    react_native:
      code: 'const profile = await textile.profile.get()'
    objc:
      code: |
        NSError *error;
        Peer *profile = [Textile.instance.profile get:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let profile = Textile.instance().profile.get(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Peer profile = Textile.instance().profile.get();'
  name:
    cmd:
      code: 'textile profile get name '
    js_http_client:
      code: 'const name = await textile.profile.name()'
    react_native:
      code: |
        const profile = await textile.profile.get()
        const name = profile.name
    objc:
      code: |
        NSError *error;
        NSString *name = [Textile.instance.profile name:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let name = Textile.instance().profile.name(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'String name = Textile.instance().profile.name();'
  set_name:
    notes: Name should equal 'Clyde' as it is used in Tour
    cmd:
      code: 'textile profile set --name="Clyde"'
    js_http_client:
      code: 'await textile.profile.setName("Clyde")'
    react_native:
      code: 'await textile.profile.setName("Clyde")'
    objc:
      code: |
        NSError *error;
        [Textile.instance.profile setName:@"Clyde" error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        Textile.instance().profile.setName("Clyde", error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Textile.instance().profile.setName("Clyde");'
  set_avatar:
    cmd:
      code: 'textile profile set --avatar="path/to/an/image"'
    js_http_client:
      code: |
        const form = new FormData()
        form.append('file', file, file.name) // file is File object or raw Buffer/Blob
        await textile.profile.setAvatar(form)
    react_native:
      code: '@todo'
    objc:
      code: '@todo'
    swift:
      code: '@todo'
    android:
      code: '@todo'
account:
  get_:
    cmd:
      code: 'textile account get'
    js_http_client:
      code: 'const account = await textile.account.get()'
    react_native:
      code: |
        // React native has endpoints for each component of the account
        const address = await textile.account.address()
    objc:
      code: 'NSString *address = [Textile.instance.account address];'
    swift:
      code: 'let adress = Textile.instance().account.address()'
    android:
      code: 'String address = Textile.instance().account.address();'
  seed:
    cmd:
      code: 'textile account seed'
    js_http_client:
      code: 'const seed = await textile.account.seed()'
    react_native:
      code: 'const seed = await textile.account.seed()'
    objc:
      code: 'NSString *seed = [Textile.instance.account seed];'
    swift:
      code: 'let seed = Textile.instance().account.seed()'
    android:
      code: 'String seed = Textile.instance().account.seed();'
  sync:
    cmd:
      code: 'textile account sync'
    js_http_client:
      code: 'await textile.account.sync()'
    react_native:
      code: 'await textile.account.sync()'
    objc:
      code: |
        NSError *error;
        QueryOptions *options = [[QueryOptions alloc] init];
        [Textile.instance.account sync:options error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let options = QueryOptions()
        Textile.instance().account.sync(options, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        QueryOptions options = QueryOptions.newBuilder().build();
        Textile.instance().account.sync(options);
contacts:
  search:
    notes: Name should equal 'Andrew' as it is used in Tour
    cmd:
      code: 'textile contacts search --name="Andrew"'
    js_http_client:
      code: |
        // The js-http-client returns a `ReadableStream` to be accessed by the caller
        // See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream for details
        const stream = await textile.contacts.search("Andrew")
    react_native:
      code: '@todo'
    objc:
      code: |
        NSError *error;
        QueryOptions *options = [[QueryOptions alloc] init];
        options.wait = 10;
        options.limit = 1;
        ContactQuery *query = [[ContactQuery alloc] init];
        query.name = @"Andrew";
        MobileSearchHandle *handle = [Textile.instance.contacts search:query options:options error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let options = QueryOptions()
        options.wait = 10
        options.limit = 1
        let query = ContactQuery()
        query.name = "Andrew"
        let handle = Textile.instance().contacts.search(query, options: options, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        QueryOptions options = QueryOptions.newBuilder()
                .setWait(10)
                .setLimit(1)
                .build();
        ContactQuery query = ContactQuery.newBuilder()
                .setName("Andrew")
                .build();
        SearchHandle handle = Textile.instance().contacts.search(query, options);
  search_address:
    notes: Address is set to match that of the Tour
    cmd:
      code: 'textile contacts search --address="P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG"'
    js_http_client:
      code: |
        // Again, `stream` is a `ReadableStream` to be accessed by the caller
        const stream = await textile.contacts.search(undefined, "P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG")
    react_native:
      code: '@todo'
    objc:
      code: |
        NSError *error;
        QueryOptions *options = [[QueryOptions alloc] init];
        options.wait = 10;
        options.limit = 1;
        ContactQuery *query = [[ContactQuery alloc] init];
        query.address = @"P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG";
        MobileSearchHandle *handle = [Textile.instance.contacts search:query options:options error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let options = QueryOptions()
        options.wait = 10
        options.limit = 1
        let query = ContactQuery()
        query.address = "P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG"
        let handle = Textile.instance().contacts.search(query, options: options, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        QueryOptions options = QueryOptions.newBuilder()
                .setWait(10)
                .setLimit(1)
                .build();
        ContactQuery query = ContactQuery.newBuilder()
                .setAddress("P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG")
                .build();
        SearchHandle handle = Textile.instance().contacts.search(query, options);
  add:
    notes: Address is set to match that of the Tour
    cmd:
      code: 'textile contacts add --address="P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG"'
    js_http_client:
      code: |
        const contact = ... // Should be a Contact object, as returned by `search` `ReadableStream`
        await textile.contacts.add("P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG", contact)
    react_native:
      code: '@todo'
    objc:
      code: |
        Contact *searchResult = // Should be a Contact object as returned by contact search
        NSError *error;
        [Textile.instance.contacts add:searchResult error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        let contact = // Should be a Contact object as returned by contact search
        var error: NSError?
        Textile.instance().contacts.add(contact, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        Contact searchResult = // Should be a Contact object as returned by contact search
        Textile.instance().contacts.add(searchResult);
  ls:
    cmd:
      code: 'textile contacts list'
    js_http_client:
      code: 'const contacts = await textile.contacts.list()'
    react_native:
      code: 'const contacts = await textile.contacts.list()'
    objc:
      code: |
        NSError *error;
        ContactList *contacts = [Textile.instance.contacts list:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let contacts = Textile.instance().contacts.list(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'ContactList contacts = Textile.instance().contacts.list();'
  rm:
    notes: Address is set to match that of the Tour
    cmd:
      code: 'textile contacts delete "P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG"'
    js_http_client:
      code: 'await textile.contacts.remove("P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG")'
    react_native:
      code: 'await textile.contacts.remove("P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG")'
    objc:
      code: |
        NSError *error;
        [Textile.instance.contacts remove:@"P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG" error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        Textile.instance().contacts.remove("P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG", error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Textile.instance().contacts.remove("P8rW2RCMn75Dcb96Eiyg8mirb8nL4ruCumvJxKZRfAdpE5fG");'
threads:
  add:
    notes: Use of 'Basic' set to match that of the Tour
    cmd:
      code: 'textile threads add "Basic"'
    js_http_client:
      code: 'const thread = await textile.threads.add("Basic")'
    react_native:
      code: |
        const threadKey = 'your.bundle.id.version.Basic'
        const threadName = 'Basic'
        const schema = {
          id: '',
          json: '',
          preset: AddThreadConfig.Schema.Preset.BLOB
        }
        const config = {
          key: threadKey,
          name: threadName,
          type: Thread.Type.PRIVATE,
          sharing: Thread.Sharing.NOT_SHARED,
          schema: schema,
          force: false,
          members: []
        }
        const newTarget = await textile.threads.add(config)
    objc:
      code: |
        NSError *error;
        AddThreadConfig_Schema *schema = [[AddThreadConfig_Schema alloc] init];
        schema.preset = AddThreadConfig_Schema_Preset_Blob;
        AddThreadConfig *config = [[AddThreadConfig alloc] init];
        config.key = @"your.bundle.id.version.Basic";
        config.name = @"Basic";
        config.type = Thread_Type_Private;
        config.sharing = Thread_Sharing_NotShared;
        config.schema = schema;
        Thread *thread = [Textile.instance.threads add:config error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let schema = AddThreadConfig_Schema()
        schema.preset = AddThreadConfig_Schema_Preset.blob
        let config = AddThreadConfig()
        config.key = "your.bundle.id.version.Basic"
        config.name = "Basic"
        config.type = Thread_Type.private
        config.sharing = Thread_Sharing.notShared
        config.schema = schema
        let thread = Textile.instance().threads.add(config, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        AddThreadConfig.Schema schema = AddThreadConfig.Schema.newBuilder()
                .setPreset(AddThreadConfig.Schema.Preset.BLOB)
                .build();
        AddThreadConfig config = AddThreadConfig.newBuilder()
                .setKey("your.bundle.id.version.Basic")
                .setName("Basic")
                .setType(Thread.Type.PRIVATE)
                .setSharing(Thread.Sharing.NOT_SHARED)
                .setSchema(schema)
                .build();
        Textile.instance().threads.add(config);
  add_open_shared:
    notes: Dogs and params are set to match that of the Tour
    cmd:
      code: 'textile threads add "Dogs" --type="open" --sharing="shared" --media'
    js_http_client:
      code: 'await textile.threads.add("Dogs", "media", "your.bundle.id.version.Media", "open", "shared")'
    react_native:
      code: |
        const threadKey = 'your.bundle.id.version.Media'
        const threadName = 'Dogs'
        const schema = {
          id: '',
          json: '',
          preset: AddThreadConfig.Schema.Preset.MEDIA
        }
        const config = {
          key: threadKey,
          name: threadName,
          type: Thread.Type.OPEN,
          sharing: Thread.Sharing.SHARED,
          schema: schema,
          force: false,
          members: []
        }
        const newTarget = await textile.threads.add(config)
    objc:
      code: |
        NSError *error;
        AddThreadConfig_Schema *schema = [[AddThreadConfig_Schema alloc] init];
        schema.preset = AddThreadConfig_Schema_Preset_Media;
        AddThreadConfig *config = [[AddThreadConfig alloc] init];
        config.key = @"your.bundle.id.version.Media";
        config.name = @"Dogs";
        config.type = Thread_Type_Open;
        config.sharing = Thread_Sharing_Shared;
        config.schema = schema;
        Thread *thread = [Textile.instance.threads add:config error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let schema = AddThreadConfig_Schema()
        schema.preset = AddThreadConfig_Schema_Preset.media
        let config = AddThreadConfig()
        config.key = "your.bundle.id.version.Media"
        config.name = "Dogs"
        config.type = Thread_Type.open
        config.sharing = Thread_Sharing.shared
        config.schema = schema
        let thread = Textile.instance().threads.add(config, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        AddThreadConfig.Schema schema = AddThreadConfig.Schema.newBuilder()
                .setPreset(AddThreadConfig.Schema.Preset.MEDIA)
                .build();
        AddThreadConfig config = AddThreadConfig.newBuilder()
                .setKey("your.bundle.id.version.Media")
                .setName("Dogs")
                .setType(Thread.Type.OPEN)
                .setSharing(Thread.Sharing.SHARED)
                .setSchema(schema)
                .build();
        Textile.instance().threads.add(config);
  blob_add:
    cmd:
      code: 'textile threads add "Any old data" --key="your.bundle.id.version.Blob" --blob'
    js_http_client:
      code: |
        const thread = await textile.threads.add("Any old data", "blob")
    react_native:
      code: |
        const threadKey = 'your.bundle.id.version.Blob'
        const threadName = 'Any old data'
        const schema = {
          id: '',
          json: '',
          preset: AddThreadConfig.Schema.Preset.BLOB
        }
        const config = {
          key: threadKey,
          name: threadName,
          type: Thread.Type.PRIVATE,
          sharing: Thread.Sharing.NOT_SHARED,
          schema: schema,
          force: false,
          members: []
        }
        const newTarget = await textile.threads.add(config)
    objc:
      code: |
        NSError *error;
        AddThreadConfig_Schema *schema = [[AddThreadConfig_Schema alloc] init];
        schema.preset = AddThreadConfig_Schema_Preset_Blob;
        AddThreadConfig *config = [[AddThreadConfig alloc] init];
        config.key = @"your.bundle.id.version.Blob";
        config.name = @"Any old data";
        config.type = Thread_Type_Private;
        config.sharing = Thread_Sharing_NotShared;
        config.schema = schema;
        Thread *thread = [Textile.instance.threads add:config error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let schema = AddThreadConfig_Schema()
        schema.preset = AddThreadConfig_Schema_Preset.blob
        let config = AddThreadConfig()
        config.key = "your.bundle.id.version.Blob"
        config.name = "Any old data"
        config.type = Thread_Type.private
        config.sharing = Thread_Sharing.notShared
        config.schema = schema
        let thread = Textile.instance().threads.add(config, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        AddThreadConfig.Schema schema = AddThreadConfig.Schema.newBuilder()
                .setPreset(AddThreadConfig.Schema.Preset.BLOB)
                .build();
        AddThreadConfig config = AddThreadConfig.newBuilder()
                .setKey("your.bundle.id.version.Blob")
                .setName("Any old data")
                .setType(Thread.Type.PRIVATE)
                .setSharing(Thread.Sharing.NOT_SHARED)
                .setSchema(schema)
                .build();
        Textile.instance().threads.add(config);
  cameraroll_add:
    cmd:
      code: 'textile threads add "New Shots" --key="your.bundle.id.version.CameraRoll" --cameraRoll'
    js_http_client:
      code: |
        const thread = await textile.threads.add("New Shots", 'cameraRoll')
    react_native:
      code: |
        const threadKey = 'your.bundle.id.version.CameraRoll'
        const threadName = 'Dogs'
        const schema = {
          id: '',
          json: '',
          preset: AddThreadConfig.Schema.Preset.CAMERA_ROLL
        }
        const config = {
          key: threadKey,
          name: threadName,
          type: Thread.Type.OPEN,
          sharing: Thread.Sharing.SHARED,
          schema: schema,
          force: false,
          members: []
        }
    objc:
      code: |
        NSError *error;
        AddThreadConfig_Schema *schema = [[AddThreadConfig_Schema alloc] init];
        schema.preset = AddThreadConfig_Schema_Preset_CameraRoll;
        AddThreadConfig *config = [[AddThreadConfig alloc] init];
        config.key = @"your.bundle.id.version.CameraRoll";
        config.name = @"New Shots";
        config.type = Thread_Type_Open;
        config.sharing = Thread_Sharing_Shared;
        config.schema = schema;
        Thread *thread = [Textile.instance.threads add:config error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let schema = AddThreadConfig_Schema()
        schema.preset = AddThreadConfig_Schema_Preset.cameraRoll
        let config = AddThreadConfig()
        config.key = "your.bundle.id.version.CameraRoll"
        config.name = "New Shots"
        config.type = Thread_Type.open
        config.sharing = Thread_Sharing.shared
        config.schema = schema
        let thread = Textile.instance().threads.add(config, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        AddThreadConfig.Schema schema = AddThreadConfig.Schema.newBuilder()
                .setPreset(AddThreadConfig.Schema.Preset.CAMERA_ROLL)
                .build();
        AddThreadConfig config = AddThreadConfig.newBuilder()
                .setKey("your.bundle.id.version.CameraRoll")
                .setName("New Shots")
                .setType(Thread.Type.OPEN)
                .setSharing(Thread.Sharing.SHARED)
                .setSchema(schema)
                .build();
        Textile.instance().threads.add(config);
  media_add:
    cmd:
      code: 'textile threads add "New Shares" --media'
    js_http_client:
      code: |
        const thread = await textile.threads.add("New Shares", 'media')
    react_native:
      code: |
        const threadKey = 'your.bundle.id.version.Media'
        const threadName = 'Dogs'
        const schema = {
          id: '',
          json: '',
          preset: AddThreadConfig.Schema.Preset.MEDIA
        }
        const config = {
          key: threadKey,
          name: threadName,
          type: Thread.Type.OPEN,
          sharing: Thread.Sharing.SHARED,
          schema: schema,
          force: false,
          members: []
        }
        const newTarget = await textile.threads.add(config)
    objc:
      code: |
        NSError *error;
        AddThreadConfig_Schema *schema = [[AddThreadConfig_Schema alloc] init];
        schema.preset = AddThreadConfig_Schema_Preset_Media;
        AddThreadConfig *config = [[AddThreadConfig alloc] init];
        config.key = @"your.bundle.id.version.Media";
        config.name = @"New Shots";
        config.type = Thread_Type_Open;
        config.sharing = Thread_Sharing_Shared;
        config.schema = schema;
        Thread *thread = [Textile.instance.threads add:config error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let schema = AddThreadConfig_Schema()
        schema.preset = AddThreadConfig_Schema_Preset.media
        let config = AddThreadConfig()
        config.key = "your.bundle.id.version.Media"
        config.name = "New Shots"
        config.type = Thread_Type.open
        config.sharing = Thread_Sharing.shared
        config.schema = schema
        let thread = Textile.instance().threads.add(config, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        AddThreadConfig.Schema schema = AddThreadConfig.Schema.newBuilder()
                .setPreset(AddThreadConfig.Schema.Preset.MEDIA)
                .build();
        AddThreadConfig config = AddThreadConfig.newBuilder()
                .setKey("your.bundle.id.version.Media")
                .setName("New Shots")
                .setType(Thread.Type.OPEN)
                .setSharing(Thread.Sharing.SHARED)
                .setSchema(schema)
                .build();
        Textile.instance().threads.add(config);
messages:
  notes: Use of threadId set to match that of the Tour
  add:
    cmd:
      code: 'textile messages add <thread-id> "hello?"'
    js_http_client:
      code: 'await textile.messages.add("<thread-id>", "hello?")'
    react_native:
      code: 'await textile.messages.add("<thread-id>", "hello?")'
    objc:
      code: |
        NSError *error;
        [Textile.instance.messages add:@"<thread-id>" body:@"hello?" error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        Textile.instance().messages.add("<thread-id>", body: "hello?", error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Textile.instance().messages.add("<thread-id>", "hello?");'
files:
  json_get:
    js_http_client:
      code: |
        // fileHash is a string hash, commonly found in block.files[index].file.hash
        const jsonData = textile.file.content(fileHash)
  get_:
    js_http_client:
      node:
        code: |
          // fileHash is a string hash, commonly found in block.files[index].file.hash
          const content = textile.file.content(fileHash)
      browser:
        code: |
          // fileHash is a string hash, commonly found in block.files[index].file.hash
          const content = textile.file.content(fileHash)
          const reader = new FileReader();
          reader.onload = function() {
              console.log(reader.result);
          }
          reader.readAsText(content);
    objc:
      code: |
        NSString *hash = @"some file hash";
        NSError *error;
        NSString *base64 = [Textile.instance.files data:hash error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        let hash = "<some file hash>"
        var error: NSError?
        let base64 = Textile.instance().files.data(hash, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        String hash = "some file hash";
        String base64 = Textile.instance().files.data(hash);
  add:
    cmd:
      code: 'echo "mmm, bytes..." | textile files add <thread-id>'
    js_http_client:
      node:
        code: 'const block = await textile.files.add("mmm, bytes...", "", "<thread-id>")'
      browser:
        code: |
          const data = new Blob(["mmm, bytes..."])
          await textile.files.add(
            data,
            "",
            "<thread-id>"
          )
    react_native:
      code: |
        const input = Buffer.from("mmm, bytes...").toString('base64')
        const result = await textile.files.prepare(input, "<thread-id>")
        const block = await textile.files.add(result.dir, "<thread-id>")
    objc:
      code: |
        NSString *threadId = @"<thread-id>";
        NSString *filePath = @"/path/to/mmm-bytes....";
        [Textile.instance.files prepareByPath:filePath threadId:threadId completion:^(MobilePreparedFiles *preparedFiles, NSError *error) {
          if (error) {
            // Do something with this error
          } else {
            NSError *addFilesError;
            [Textile.instance.files add:preparedFiles.dir threadId:threadId caption:nil error:&addFilesError];
            if (addFilesError) {
              // Do something with this error
            } else {
              // Success!
            }
          }
        }];
    swift:
      code: |
        let threadId = "<thread-id>"
        let filePath = "/path/to/mmm-bytes...."
        Textile.instance().files.prepare(byPath: filePath, threadId: threadId) { (preparedFiles, error) in
          if let files = preparedFiles {
            var addFilesError: NSError?
            Textile.instance().files.add(files.dir, threadId: threadId, caption: nil, error: &addFilesError)
            if (addFilesError != nil) {
              // Do something with this error
            } else {
              // Success!
            }
          } else {
            // Do something with error
          }
        }
    android:
      code: |
        final String threadId = "<thread-id>";
        String filePath = "/path/to/mmm-bytes....";
        Textile.instance().files.prepareByPath(filePath, threadId, new PreparedFilesHandler() {
            @Override
            public void onFilesPrepared(MobilePreparedFiles preparedFiles) {
                try {
                    Textile.instance().files.add(preparedFiles.getDir(), threadId, null);
                } catch (Exception e) {
                    // Do something with this error
                }
            }

            @Override
            public void onError(Exception e) {
                // Do something with this error
            }
        });
  add_media:
    cmd:
      code: 'textile files add <thread-id> ~/Downloads/william-milliot-510766-unsplash.jpg --caption="Dog at work."'
    js_http_client:
      code: |
        const form = new FormData()
        form.append('file', file, file.name) // file is File object or raw Buffer/Blob or even fs.createReadStream() in Node.js
        await textile.files.add(base64ImageString, "Dog at work", "<thread-id>")'
    react_native:
      code: |
        const base64ImageString = "R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABA...."
        const result = await textile.files.prepare(base64ImageString, "<thread-id>")
        const block = await textile.files.add(result.dir, "<thread-id>")
    objc:
      code: |
        NSString *threadId = @"<thread-id>";
        NSString *filePath = @"/path/to/image";
        [Textile.instance.files prepareByPath:filePath threadId:threadId completion:^(MobilePreparedFiles *preparedFiles, NSError *error) {
          if (error) {
            // Do something with this error
          } else {
            NSError *addFilesError;
            [Textile.instance.files add:preparedFiles.dir threadId:threadId caption:nil error:&addFilesError];
            if (addFilesError) {
              // Do something with this error
            } else {
              // Success!
            }
          }
        }];
    swift:
      code: |
        let threadId = "<thread-id>"
        let filePath = "/path/to/image"
        Textile.instance().files.prepare(byPath: filePath, threadId: threadId) { (preparedFiles, error) in
          if let files = preparedFiles {
            var addFilesError: NSError?
            Textile.instance().files.add(files.dir, threadId: threadId, caption: nil, error: &addFilesError)
            if (addFilesError != nil) {
              // Do something with this error
            } else {
              // Success!
            }
          } else {
            // Do something with error
          }
        }
    android:
      code: |
        final String threadId = "<thread-id>";
        String filePath = "/path/to/image";
        Textile.instance().files.prepareByPath(filePath, threadId, new PreparedFilesHandler() {
            @Override
            public void onFilesPrepared(MobilePreparedFiles preparedFiles) {
                try {
                    Textile.instance().files.add(preparedFiles.getDir(), threadId, null);
                } catch (Exception e) {
                    // Do something with this error
                }
            }

            @Override
            public void onError(Exception e) {
                // Do something with this error
            }
        });
  keys_:
    cmd:
      code: 'textile files keys "<block-target>"'
    js_http_client:
      code: 'const keys = await textile.files.keys("<block-target>")'
    react_native:
      code: '@todo'
    objc:
      code: '@todo'
    swift:
      code: '@todo'
    android:
      code: '@todo'
cafes:
  tokens_create:
    cmd:
      code: 'textile tokens add --api="http://127.0.0.1:41600"'
    js_http_client:
      code: |
        const cafe = Textile.create({ url: "http://127.0.0.1", port: "41600" })
        const token = await cafe.tokens.add()
  tokens_list:
    cmd:
      code: 'textile tokens ls --api="http://127.0.0.1:41600"'
    js_http_client:
      code: 'const tokens = await textile.tokens.list()'
  add:
    cmd:
      code: 'textile cafes add <cafe-peer-id> --token="<token-string>"'
    js_http_client:
      code: |
        const success = await textile.cafes.add("<cafe-peer-id>", "<token-string>")
    react_native:
      code: |
        await textile.cafes.register("<cafe-peer-id>", "<token-string>")
    objc:
      code: |
        NSString *token = @"bYJLFjHsRsZjdzEwC2pJwQthmfYb3DPYyBCcU49Dkfqd5xGHk5NR77X8GDKG";
        NSError *error;
        [Textile.instance.cafes register:@"<cafe-peer-id>" token:token error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        let token = "<token-string>"
        var error: NSError?
        Textile.instance().cafes .register("<cafe-peer-id>", token: token, error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: |
        String token = "<token-string>";
        Textile.instance().cafes.register("<cafe-peer-id>", token);
  list:
    cmd:
      code: 'textile cafes ls'
    js_http_client:
      code: 'const sessions = await textile.cafes.list()'
    objc:
      code: |
        NSError *error;
        CafeSessionList *sessions = [Textile.instance.cafes sessions:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let sessions = Textile.instance().cafes.sessions(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'CafeSessionList sessions = Textile.instance().cafes.sessions();'
  messages:
    cmd:
      code: 'textile cafes messages'
    js_http_client:
      code: 'const success = await textile.cafes.messages()'
    objc:
      code: |
        NSError *error;
        [Textile.instance.cafes checkMessages:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        Textile.instance().cafes.checkMessages(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Textile.instance().cafes.checkMessages();'
  remove:
    cmd:
      code: 'textile cafes rm "12D3KooW9yaALxxk31nnaPZB9tzjwxFyPUBrwLuCXZ3FnAWg8VyV"'
    js_http_client:
      code: 'const success = await textile.cafes.remove("12D3KooW9yaALxxk31nnaPZB9tzjwxFyPUBrwLuCXZ3FnAWg8VyV")'
    objc:
      code: |
        NSError *error;
        [Textile.instance.cafes deregister:@"12D3KooW9yaALxxk31nnaPZB9tzjwxFyPUBrwLuCXZ3FnAWg8VyV" error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        Textile.instance().cafes.deregister("12D3KooW9yaALxxk31nnaPZB9tzjwxFyPUBrwLuCXZ3FnAWg8VyV", error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'Textile.instance().cafes.deregister("12D3KooW9yaALxxk31nnaPZB9tzjwxFyPUBrwLuCXZ3FnAWg8VyV");'
ipfs:
  peer_id:
    cmd:
      code: 'textile ipfs id'
    js_http_client:
      code: 'const id = await textile.ipfs.id()'
    react_native:
      code: 'const id = await textile.ipfs.peerId()'
    objc:
      code: |
        NSError *error;
        NSString *peerId = [Textile.instance.ipfs peerId:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let peerId = Textile.instance().ipfs.peerId(&error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'String peerId = Textile.instance().ipfs.peerId();'
  swarm_peers:
    cmd:
      code: 'textile ipfs swarm peers'
    js_http_client:
      code: 'const peers = await textile.ipfs.peers()'
  swarm_connect:
    cmd:
      code: 'textile ipfs swarm connect "/ip4/18.224.173.65/tcp/4001/ipfs/12D3KooWLh9Gd4C3knv4XqCyCuaNddfEoSLXgekVJzRyC5vsjv5d"'
    js_http_client:
      code: 'const success = await textile.ipfs.connect("/ip4/18.224.173.65/tcp/4001/ipfs/12D3KooWLh9Gd4C3knv4XqCyCuaNddfEoSLXgekVJzRyC5vsjv5d")'
  cat:
    cmd:
      code: 'textile ipfs cat "QmarZwQEri4g2s8aw9CWKhxAzmg6rnLawGuSGYLSASEow6/0/d" > textile.png'
    js_http_client:
      code: 'const logo = await textile.ipfs.cat("QmarZwQEri4g2s8aw9CWKhxAzmg6rnLawGuSGYLSASEow6/0/d")'
    objc:
      code: |
        NSError *error;
        NSData *data = [Textile.instance.ipfs dataAtPath:@"QmarZwQEri4g2s8aw9CWKhxAzmg6rnLawGuSGYLSASEow6/0/d" error:&error];
        if (error) {
          // Do something with this error
        } else {
          // Success!
        }
    swift:
      code: |
        var error: NSError?
        let data = Textile.instance().ipfs.data(atPath: "QmarZwQEri4g2s8aw9CWKhxAzmg6rnLawGuSGYLSASEow6/0/d", error: &error)
        if (error != nil) {
          // Do something with this error
        } else {
          // Success!
        }
    android:
      code: 'byte[] data = Textile.instance().ipfs.dataAtPath("QmarZwQEri4g2s8aw9CWKhxAzmg6rnLawGuSGYLSASEow6/0/d");'
  pubsub_sub:
    cmd:
      code: 'textile ipfs pubsub sub SomeTopicEgThreadId'
    js_http_client:
      code: |
        let useEventSource = EventSource !== undefined;
        let {
            queryId,
            queryHandle,
        } = await textile.ipfs.pubsubSub('SomeTopicEgThreadId', useEventSource);
        // note: The second param of js_http_client pubsubSub() is optional, so all code here is
        also can be used in react_native

        let allEventsAndQueryHandle = textile.events.addPubsubQueryResultListener((queryId, message, messageId) => {
          console.warn(queryId, message, messageId);
        }, queryId, queryHandle);
        // note: For easy to use, allEventsAndQueryHandle.cancel() will remove all events added in
        //           addPubsubQueryResultListener()
        //           addQueryDoneListener()
        //           addQueryErrorListener()
        //      and cancel queryHandle automatically

        doneEvent = textile.events.addQueryDoneListener((queryId) => {
            console.warn('doneEvent', queryId);
            // For easy to use, it's no need to run allEventsAndQueryHandle.cancel() or doneEvent.cancel()
            // here, because they are already canceled automatically when this event happend and
            // finally call this callback function of QueryDoneListener
        }, queryId);
        // note: User can use doneEvent.cancel() at any other place as wish to just cancel QueryDoneListener

        this.errorEvent = textile.events.addQueryErrorListener((queryId, error) => {
            console.warn('errorEvent', error);
            // It's up to user to determin what to do (e.g. allEventsAndQueryHandle.cancel() or just continue?)
            // when error happend
        }, queryId);
        // note: User can use errorEvent.cancel() at any other place as wish to just cancel QueryErrorListener
    react_native:
      code: |
        let {
            queryId,
            queryHandle,
        } = await textile.ipfs.pubsubSub('SomeTopicEgThreadId');
        // note: The second param of js_http_client pubsubSub() is optional, so all code here is
        also can be used in react_native

        let allEventsAndQueryHandle = textile.events.addPubsubQueryResultListener((queryId, message, messageId) => {
          console.warn(queryId, message, messageId);
        }, queryId, queryHandle);
        // note: For easy to use, allEventsAndQueryHandle.cancel() will remove all events added in
        //           addPubsubQueryResultListener()
        //           addQueryDoneListener()
        //           addQueryErrorListener()
        //      and cancel queryHandle automatically

        doneEvent = textile.events.addQueryDoneListener((queryId) => {
            console.warn('doneEvent', queryId);
            // For easy to use, it's no need to run allEventsAndQueryHandle.cancel() or doneEvent.cancel()
            // here, because they are already canceled automatically when this event happend and
            // finally call this callback function of QueryDoneListener
        }, queryId);
        // note: User can use doneEvent.cancel() at any other place as wish to just cancel QueryDoneListener

        this.errorEvent = textile.events.addQueryErrorListener((queryId, error) => {
            console.warn('errorEvent', error);
            // It's up to user to determin what to do (e.g. allEventsAndQueryHandle.cancel() or just continue?)
            // when error happend
        }, queryId);
        // note: User can use errorEvent.cancel() at any other place as wish to just cancel QueryErrorListener
    objc:
      code: |
        NSError *error;
        NSString *queryId = [Textile.instance.ipfs pubsubSub:@"SomeTopicEgThreadId" error:&error];

        // Set the Textile delegate to any object that conforms to the TextileDelegate protocol
        Textile.instance.delegate = delegate;

        // The delegate can receive callbacks for any event it is interested in, for example
        - (void)pubsubQueryResult:(NSString *)queryId message:(NSString *)message messageId:(NSString *)messageId {
          // new ipfs pubsub message!
        }
    swift:
      code: |
        var queryId = Textile.instance().ipfs.pubsubSub(topic: "SomeTopicEgThreadId", error: SomeUserDefinedNSError);

        // Set the Textile delegate to any object that conforms to the TextileDelegate protocol
        Textile.instance().delegate = delegate

        // The delegate can receive callbacks for any event it is interested in, for example
        func pubsubQueryResult(_ queryId: String, _ message: String, _ messageId: String) {
          // new ipfs pubsub message!
        }
    android:
      code: |
        String queryId = Textile.instance().ipfs.pubsubSub("SomeTopicEgThreadId");

        // Add an event listener that conforms to TextileEventListener
        class MyEventListener extends BaseTextileEventListener {
            @Override
            public void pubsubQueryResult(final String queryId, final String message, final String messageId) {
                // new ipfs pubsub message!
            }
        }
        Textile.instance().addEventListener(new MyEventListener());
