JMP

XMPPTwitterReddit
Featured Image

Newsletter: JMP is 6! Leaving beta this year! And FOSSY 🙂️

denver@ozg.ca

Hi everyone!

Welcome to the latest edition of your pseudo-monthly JMP update!

In case it’s been a while since you checked out JMP, here’s a refresher: JMP lets you send and receive text and picture messages (and calls) through a real phone number right from your computer, tablet, phone, or anything else that has a Jabber client.  Among other things, JMP has these features: Your phone number on every device; Multiple phone numbers, one app; Free as in Freedom; Share one number with multiple people.

JMP is 6 years old today!  When we launched in 2017 we had no idea exactly how far we’d go, or that we’d be making one of the most popular chat clients on F-Droid (that is Cheogram Android, which is based on Conversations).  Last year we called JMP “phone-feature-complete” and since then we’ve made all of JMP’s features even easier to use, shepherding big improvements to various Jabber clients, including Movim and Dino, while continuing to provide first-class telephony and messaging features in our flagship mobile app: Cheogram Android.

With so many of the edges now smoothed, and a new onboarding flow almost ready to go, it’s now time to announce: JMP will be leaving beta this year!

What does that mean?  Primarily this is our vote of confidence (as the JMP and Cheogram team) that JMP, and apps we develop such as Cheogram Android, are ready for widespread use.  While of course there will still be improvements to make, we believe it will be able to be recommended to your friends and family (especiall Android users) without reservation.

Naturally there are a couple things to do yet to make that happen, and one of them is to put Cheogram Android in the Play Store at last.  This will be a paid (but still free-as-in-freedom) app that will include one month of JMP service.  Of course, you will still be able to get Cheogram Android from all the other places you can already get it (such as F-Droid and our own repos/APKs).

The other main thing is to set a final post-beta monthly price for JMP.  And, while it won’t take effect until we launch later this year, we are able to now officially announce it: US$4.99/month, with incidental pricing remaining the same (i.e. extra/international minutes will remain what they are now).  Note that there will be discounts for additional JMP numbers linked to your primary JMP number, and also (before JMP leaves beta) a chance to lock in the existing pricing for a period of time.  Having never changed the price since we started JMP 6 years ago, and given the inflation and our own staffing costs since then, we feel the new price will allow JMP to remain both sustainable, and able to face new challenges and exciting opportunities going forward (like the EU’s DMA, for one).  We want to make JMP the best phone number service, and Cheogram the best gateway to everything in the world!

Speaking of Cheogram, a JMP newsletter these days wouldn’t be complete without mention of new Cheogram Android features (2.12.1-2 released in APK form and Cheogram F-Droid repo today!):

  • it will now offer to setup Dialer integration automatically when available
  • the Call Logs (cdrs) command replaces the usage command (giving you more info)
  • the new onboarding flow is improved even more
  • admins of a Snikket instance can create a new Jabber ID and JMP number all inside the app now (see the video demo)
  • new theme: any colour you want! (requires Android 11 or higher)

Note that the Call Logs (cdrs) command will roll out to everyone in about a week.  If you’d like to try it before then, please send a private inquiry to JMP support and we’ll activate it for you.

Lastly, some of you may be interested to know that the JMP/Cheogram team are going to be venturing out to a conference for the first time since March 2020!  In particular, most of the JMP/Cheogram team will be attending FOSSY this year, in Portland, Oregon, USA this July 13-16.  We’ll be announcing specifics of our involvement (whether we have a booth, talks, etc.) closer to the dates.  In the meantime, just know we’ll be there, and would love to chat with any JMP/Cheogram users, prospective customers, or otherwise!

With that, we’ll cap off our 6 years. :)  And what an exciting 6 years it’s been!  With the big launch this year, you can bet on many more to come!

To learn what’s happening with JMP between newsletters, here are some ways you can find out:

Thanks for reading and have a wonderful rest of your week!

Featured Image

Newsletter: Threads, Thumbnails, XMR, ETH

singpolyma@singpolyma.net

Hi everyone!

Welcome to the latest edition of your pseudo-monthly JMP update!

In case it’s been a while since you checked out JMP, here’s a refresher: JMP lets you send and receive text and picture messages (and calls) through a real phone number right from your computer, tablet, phone, or anything else that has a Jabber client.  Among other things, JMP has these features: Your phone number on every device; Multiple phone numbers, one app; Free as in Freedom; Share one number with multiple people.

This month we released Cheogram Android 2.12.1-1 which includes several new features.  One of the big ones is an interface for having threaded conversations with other Jabber users (watch the demo video).  This feature will also make it easier to reply to the right message if you use the email gateway.  The app has grown support for more media features, including an ability to show an image right away if you already have it, without waiting for a download, and blurhash based placeholders for images from MMS you have not yet downloaded.

There is also a new user experience when receiving group texts that will actually show the sender’s name (and even avatar, if you have one set for them locally) the same way as any other group chat in the app.  This is made possible by a new draft protocol extension we adopted for part of the purpose.

This version is based on the latest 2.12.1 from upstream, which among other things has added the ability to function as a Unified Push distributor, so if you use any compatible app you may want to check that out.

For the JMP service, this month we shipped the ability to make top-up payments using XMR or ETH directly from the top up command.  This simplifies the flow for users of those currencies, and we hope you will find it useful.  Integrating this support into registration is also coming, but not ready quite yet.

If you are planning to be at FOSDEM 2023, be sure to check out the realtime lounge in with the other stands.  Unfortunately no one from JMP will be there this year, but people from Snikket and other projects around the ecosystem will be present.

To learn what’s happening with JMP between newsletters, here are some ways you can find out:

Thanks for reading and have a wonderful rest of your week!

Featured Image

Newsletter: Busy Year in 2022

singpolyma@singpolyma.net

Hi everyone!

Welcome to the latest edition of your pseudo-monthly JMP update!

In case it’s been a while since you checked out JMP, here’s a refresher: JMP lets you send and receive text and picture messages (and calls) through a real phone number right from your computer, tablet, phone, or anything else that has a Jabber client.  Among other things, JMP has these features: Your phone number on every device; Multiple phone numbers, one app; Free as in Freedom; Share one number with multiple people.

Cheogram Android 2.11.0-1 has been released, including an important fix for creating new private group chats.  For some months creating such a group (a Jabber group, not a “group text”) with Cheogram Android has resulted in a public channel on many servers.  Please double-check your private groups and change settings if necessary!  This release will also be the first accepted into F-Droid with an up-to-date version of libwebrtc, so if you’ve had any issues with calls and use the F-Droid build, we recommend upgrading and trying again.  This release also adds support for tagging channels and group chats (on supporting servers, such as Snikket), better use of locales to determine what country code to prepend when dialling, a new OLED black theme, and more.

The data plan roll out continues, accelerating in December but we know there are still many of you waiting.  Thank you so much for your patience, and to all the feedback we have received from users so far.  We are actively working on making the signup process self-serve so that the waitlist will no longer be necessary in the future.

When JMP started we were just one part-time person.  As we grow, the legal structures that fit that time no longer do.  This fall we incorporated the MBOA Technology Co-operative to house JMP, Togethr, consulting work, and other activity.  This gives all our employees full agency in the company and gives us a firm legal footing for the future.  Nothing changes for you at this time, we’re still the same team, and for the time being you don’t even change the name you write on the cheques, nevertheless it marks a milestone in our life as a company.

Year in Review

This year, JMP and Snikket CIC made a deal to offer Jabber hosting as an option for JMP customers. This service is included in the regular JMP subscription and will eventually be the default option for new users during the sign-up process. JMP customers have been able to participate in a beta version of this integration, and JMP customers can contact JMP support to set up a Snikket instance directly.

This year also saw international calling added to our list of features. JMP users are able to use as many minutes per month as they like, with approximately 120 minutes of credit to USA and Canada included by default. Customers are able to pay for additional minutes and make international calls, although users who are still paying with the old PayPal system will not have access to these features (or other features such as the data plan). We also implemented a per-calendar-month overage limit system, where customers can set their own limits to avoid unexpected charges. The default limit is currently set at $0/month.

One of our most popular features has always been our voicemail and transcription, this year we expanded that to support multi-lingual transcriptions as well.

We also added multi-account billing this year, an alpha for JMP use from Matrix, added two employees, created new bot commands for account management, launched Togethr to help people take control of their social media identity, added support for SMS-only ports and the option to disable voicemail, built an XMPP integration for Chatwoot, and launched the JMP data plan.

This year saw the launch and rapid development of the Cheogram Android app, forked from Conversations and including these and other improvements:

  • Add contacts without typing @cheogram.com
  • Integrate with the native Android Phone app (optional)
  • Address book integration (optional)
  • Option to start group texts easily
  • Command UI for better interactions with our and other bots (you can even sign up entirely from within the app!)
  • Rich text message display (including stickers from Movim users)
  • Data de-duplication for files sent/received multiple times
  • Message retraction
  • Ability to edit tags on contacts and channels
  • Tag navigation widget for easier conversation management
  • Ability to copy any link in a message to the clipboard
  • F-Droid repositories for quick updates of official builds

Blog posts this year included: How to use Jabber from SMS, Why Bidirectional Gateways Matter, Computing International Call Rates with a Trie, Privacy and Threat Modelling, SMS Account Verification, and Writing a Chat Client from Scratch.

To learn what’s happening with JMP between newsletters, here are some ways you can find out:

Thanks for reading and have a wonderful rest of your week!

Featured Image

Writing a Chat Client from Scratch

singpolyma@singpolyma.net

There are a lot of things that go into building a chat system, such as client, server, and protocol.  Even for only making a client there are lots of areas of focus, such as user experience, features, and performance.  To keep this post a manageable size, we will just be building a client and will use an existing server and protocol (accessing Jabber network services using the XMPP protocol).  We’ll make a practical GUI so we can test things, but not spend too much time on polish, and look at getting to a useful baseline of features.

You can find all the code for this post in git.  All code licensed AGPL3+.

Use a Library

As with most large programming tasks, if we wanted to do every single thing ourselves we would spend a lot more time, so we should find some good libraries.  There is another reason to use a library: any improvements we make to the library benefits others.  While releasing our code might help someone else if they choose to read it, a library improvement can be picked up by users of that library right away.

We need to speak the XMPP protocol so let’s choose Blather.  We need a GUI so we can see this working, but don’t really want to futz with it much so let’s choose Glimmer.  The code here will use these libraries and be written in the Ruby programming language, but these ideas are general purpose to the task and hopefully we won’t get too bogged down in syntax specifics.

One little language-specific thing you will need to create is a description of which ruby packages are being used, so let’s make that file (named Gemfile):

Gemfile

source "https://rubygems.org"

gem "blather", git: "https://github.com/adhearsion/blather", branch: "develop"
gem "glimmer-dsl-libui", "~> 0.5.24"

Run this to get the packages installed:

bundle install --path=.gems

Let’s get the bare minimum: a connection to a Jabber service and a window.

client.rb

require "glimmer-dsl-libui"
require "blather/client"

BLATHER = self
include Glimmer

Thread.new do
	window("Contacts") {
		on_destroy {
			BLATHER.shutdown
		}
	}
end

When required in this way, Blather will automatically set up a connection with event processing on the main thread, and will process command line arguments to get connection details.  So we put the GUI on a second thread to not have them block each other.  When the window is closed (on_destroy), be sure to disconnect from the server too.  You can run this barely-a-client like this:

bundle exec ruby client.rb user@example.com password

The arguments are a Jabber ID (which you can get from many existing services), and the associated password.

You should get a blank window and no errors in your terminal.  If you wanted to you could even look in another client and confirm that it is connected to the account by seeing it come online.

Show a Contact List

Let’s fetch the user’s contacts from the server and show them in the window (if you use this with a new, blank test account there won’t be any contacts yet of course, but still).

$roster = [["", ""]]

Thread.new do
	window("Contacts") {
		vertical_box {
			table {
				button_column("Contact") {
				}
				editable false
				cell_rows $roster
			}
		}

		on_destroy {
			BLATHER.shutdown
		}
    }.show
end

after(:roster) do
	LibUI.queue_main do
		$roster.clear
		my_roster.each do |item|
			$roster << [item.name || item.jid, item.jid]
		end
	end
end

In a real app you would probably want some kind of singleton object to represent the contacts window and the contact list (“roster”) etc.  For simplicity here we just use a global variable for the roster, starting with some dummy data so that the GUI framework knows what it will look like, etc.

We fill out the window from before a little bit to have a table with a column of buttons, one for each contact.  The button_column is the first (and in this case, only) column definition so it will source data from the first element of each item in cell_rows.  It’s not an editable table, and it gets its data from the global variable.

We then add an event handler to our XMPP connection to say that once the roster has been loaded from the server, we hand control over to the GUI thread and there we clear out the global variable and fill it up with the roster as we now see it.  The first item in each row is the name that will be shown on the button (either item.name or item.jid if there is no name set), the second item is the Jabber ID which won’t be shown because we didn’t define that column when we made the window.  Any updates to the global variable will be automatically painted into the GUI so we’re done.

One Window Per Conversation

For simplicity, let’s say we want to show one window per conversation, like so:

$conversations = {}

class Conversation
	include Glimmer

	def self.open(jid, m=nil)
		return if $conversations[jid]

		($conversations[jid] = new(jid, m)).launch
	end

	def initialize(jid, m=nil)
		@jid = jid
		@messages = [["", ""]]
		new_message(m) if m
	end

	def launch
		window("Conversation With #{@jid}") {
			vertical_box {
				table {
					text_column("Sender")
					text_column("Message")
					editable false
					cell_rows @messages
					@messages.clear
				}

				horizontal_box {
					stretchy false

					@message_entry = entry
					button("Send") {
						stretchy false

						on_clicked do
							BLATHER.say(@jid, @message_entry.text)
							@messages << [ARGV[0], @message_entry.text]
							@message_entry.text = ""
						end
					}
				}
			}

			on_closing do
				$conversations.delete(@jid)
			end
		}.show
	end

	def format_sender(jid)
		BLATHER.my_roster[jid]&.name || jid
	end

	def message_row(m)
		[
			format_sender(m.from&.stripped || BLATHER.jid.stripped),
			m.body
		]
	end

	def new_message(m)
		@messages << message_row(m)
	end
end

message :body do |m|
	LibUI.queue_main do
		conversation = $conversations[m.from.stripped.to_s]
		if conversation
			conversation.new_message(m)
		else
			Conversation.open(m.from.stripped.to_s, m)
		end
	end
end

Most of this is the window definition again, with a table of the messages in this conversation sourced from an instance variable @messages.  At the bottom of the window is an entry box to type in text and a button to trigger sending it as a message.  When the button is clicked, send that message to the contact this conversation is with, add it to the list of messages so that it shows up in the GUI, and make the entry box empty again.  When the window closes (on_closing this time because it’s not the “main” window) delete the object from the global set of open conversations.

This object also has a helper to open a conversation window if there isn’t already one with a given Jabber ID (jid), some helpers to format message objects into table rows by extracting the sender and body (including format_sender which gets the roster item if there is one, uses &.name to get the name if there was a roster item or else nil, and if there was no roster item or no name just show jid) and a helper that adds new messages into the GUI.

Finally we add a new XMPP event handler for incoming messages that have a body.  Any such incoming message we look up in the global if there is a conversation open already, if so we pass the new message there to have it appended to the GUI table, otherwise we open the conversation with this message as the first thing it will show.

Getting from the Contact List to a Conversation

Now we wire up the contact list to the conversation view:

button_column("Contact") {
	on_clicked do |row|
		Conversation.open($roster[row][1].to_s)
	end
}

When a contact button is clicked, grab the Jabber ID from the hidden end of the table row that we had stashed there, and open the conversation.

horizontal_box {
	stretchy false

	jid_entry = entry {
		label("Jabber ID")
	}

	button("Start Conversation") {
		stretchy false

		on_clicked do
			Conversation.open(jid_entry.text)
		end
	}
}

And let’s provide a way to start a new conversation with an address that isn’t a contact too.  An entry to type in a Jabber ID and a button that opens the conversation.

Adding a Contact

Might as well add a button to the main window that re-uses that entry box to allow adding a contact as well:

button("Add Contact") {
	stretchy false

	on_clicked do
		BLATHER.my_roster << jid_entry.text
	end
}

Handling Multiple Devices

In many chat protocols, it is common to have multiple devices or apps connected simultaneously. It is often desirable to show messages sent to or from one device on all the others as well.  So let’s implement that.  First, a helper for creating XML structures we may need:

def xml_child(parent, name, namespace)
	child = Niceogiri::XML::Node.new(name, parent.document, namespace)
	parent << child
	child
end

We need to tell the server that we support this feature:

when_ready do
	self << Blather::Stanza::Iq.new(:set).tap { |iq|
		xml_child(iq, :enable, "urn:xmpp:carbons:2")
	}
end

We will be handling live messages from multiple event handlers so let’s pull the live message handling out into a helper:

def handle_live_message(m, counterpart: m.from.stripped.to_s)
	LibUI.queue_main do
		conversation = $conversations[counterpart]
		if conversation
			conversation.new_message(m)
		else
			Conversation.open(counterpart, m)
		end
	end
end

And the helper that will handle messages from other devices of ours:

def handle_carbons(fwd, counterpart:)
	fwd = fwd.first if fwd.is_a?(Nokogiri::XML::NodeSet)
	return unless fwd

	m = Blather::XMPPNode.import(fwd)
	return unless m.is_a?(Blather::Stanza::Message) && m.body.present?

	handle_live_message(m, counterpart: counterpart.call(m))
end

This takes in the forwarded XML object (allowing for it to be a set of which we take the first one) and imports it with Blather’s logic to become hopefully a Message object.  If it’s not a Message or has no body, we don’t really care so we stop there. Otherwise we can handle this extracted message as though we had received it ourselves.

And then wire up the event handlers:

message(
	"./carbon:received/fwd:forwarded/*[1]",
	carbon: "urn:xmpp:carbons:2",
	fwd: "urn:xmpp:forward:0"
) do |_, fwd|
	handle_carbons(fwd, counterpart: ->(m) { m.from.stripped.to_s })
end

Because XMPP is just XML, we can use regular XPath stuff to extract from incoming messages.  Here we say that if the message contains a forwarded element inside a carbons received element, then we should handle this with the carbons handler instead of just the live messages handler.  The XML that matches our XPath comes in as the second argument and that is what we pass to the handler to get converted into a Message object.

message(
	"./carbon:sent/fwd:forwarded/*[1]",
	carbon: "urn:xmpp:carbons:2",
	fwd: "urn:xmpp:forward:0"
) do |_, fwd|
	handle_carbons(fwd, counterpart: ->(m) { m.to.stripped.to_s })
end

This handler is for messages sent by other devices instead of received by other devices.  It is pretty much the same, except that we know the “other side of the conversation” (here called counterpart) is in the to not the from.

message :body do |m|
	handle_live_message(m)
end

And our old message-with-body handler now just needs to call the helper.

History

So far our client only processes and displays live messages.  If you close the app, or even close a conversation window, the history is gone.  If you chat with another client or device, you can’t see that when you re-open this one.  To fix that we’ll need to store messages persistently, and also fetch any history from while we were disconnected from the server.  We will need a few more lines in our Gemfile first:

gem "sqlite3"
gem "xdg"

And then to set up a basic database schema:

require "securerandom"
require "sqlite3"
require "xdg"

DATA_DIR = XDG::Data.new.home + "jabber-client-demo"
DATA_DIR.mkpath
DB = SQLite3::Database.new(DATA_DIR + "db.sqlite3")

if DB.user_version < 1
	DB.execute(<<~SQL)
		CREATE TABLE messages (
			mam_id TEXT PRIMARY KEY,
			stanza_id TEXT NOT NULL,
			conversation TEXT NOT NULL,
			created_at INTEGER NOT NULL,
			stanza TEXT NOT NULL
		)
	SQL
	DB.execute("CREATE TABLE data (key TEXT PRIMARY KEY, value TEXT)")
	DB.user_version = 1
end

user_version is a SQLite feature that allows storing a simple integer alongside the database.  It starts at 0 if never set, and so here we use it to check if our schema has been created or not.  We store the database in a new directory created according to the XDG Base Directory specification.  There are two relevant IDs for most XMPP operations: the MAM ID (the ID in the server’s archive) and the Stanza ID (which was usually selected by the original sender).  We also create a data table for storing basic key-value stuff, which we’ll use in a minute to remember where we have sync’d up to so far.  Let’s edit the Conversation object to store messages as we send them, updating the send button on_clicked handler:

def message
	Blather::Stanza::Message.new(@jid, @message_entry.text, :chat).tap { |m|
		m.id = SecureRandom.uuid
	}
end

…

on_clicked do
	m = message
	EM.defer do
		BLATHER << m
		DB.execute(<<~SQL, [nil, m.id, @jid, m.to_s])
			INSERT INTO messages
			(mam_id, stanza_id, conversation, created_at, stanza)
			VALUES (?,?,?,unixepoch(),?)
		SQL
	end
	@messages << message_row(m)
	@message_entry.text = ""
end

When we send a message we don’t yet know the server’s archive ID, so we set that to nil for now.  We set mam_id to be the primary key, but SQLite allows multiple rows to have NULL in there so this will work.  We don’t want to block the GUI thread while doing database work so we use EM.defer to move this to a worker pool.  We also want to store messages when we receive them live, so add this to the start of handle_live_message:

mam_id = m.xpath("./ns:stanza-id", ns: "urn:xmpp:sid:0").find { |el|
	el["by"] == jid.stripped.to_s
}&.[]("id")
delay = m.delay&.stamp&.to_i || Time.now.to_i
DB.execute(<<~SQL, [mam_id, m.id, counterpart, delay, m.to_s])
	INSERT INTO messages (mam_id, stanza_id, conversation, created_at, stanza)
    VALUES (?,?,?,?,?)
SQL

Here we extract the server archive’s ID for the message (added by the server in a stanza-id with by="Your Jabber ID") and figure out what time the message was originally sent (usually this is just right now for a live message, but if it is coming from offline storage because every client was offline or similar, then there can be a “delay” set on it which we can use).  Now that we have stored the history of message we received we need to load them into the GUI when we start up a Conversation so add this at the end of initialize:

EM.defer do
	mam_messages = []
	query = <<~SQL
		SELECT stanza
		FROM messages
		WHERE conversation=?
		ORDER BY created_at
	SQL
	DB.execute(query, [@jid]) do |row|
		m = Blather::XMPPNode.import(
			Nokogiri::XML.parse(row[0]).root
		)
		mam_messages << m
	end

	LibUI.queue_main do
		mam_messages.map! { |m| message_row(m) }
		@messages.replace(mam_messages + @messages)
	end
end

In the worker pool we load up all the stored messages for the current conversation in order, then we take the XML stored as a string in the database and parse it into a Blather Message object.  Once we’ve done as much of the work as we can in we worker pool we use queue_main to switch back to the GUI thread and actually build the rows for the table and replace them into the GUI.

With these changes, we are now storing all messages we see while connected and displaying them in the conversation.  But what about messages sent or received by other devices or clients while we were not connected?  For that we need to sync with the server’s archive, fetching messages at a reasonable page size from whatever we already have until the end.

def sync_mam(last_id)
	start_mam = Blather::Stanza::Iq.new(:set).tap { |iq|
		xml_child(iq, :query, "urn:xmpp:mam:2").tap do |query|
			xml_child(query, :set, "http://jabber.org/protocol/rsm").tap do |rsm|
				xml_child(rsm, :max, "http://jabber.org/protocol/rsm").tap do |max|
					max.content = (EM.threadpool_size * 5).to_s
				end
				next unless last_id

				xml_child(rsm, :after, "http://jabber.org/protocol/rsm").tap do |after|
					after.content = last_id
				end
			end
		end
	}

	client.write_with_handler(start_mam) do |reply|
		next if reply.error?

		fin = reply.find_first("./ns:fin", ns: "urn:xmpp:mam:2")
		next unless fin

		handle_rsm_reply_when_idle(fin)
	end
end

The first half of this creates the XML stanza to request a page from the server’s archive. We create a query with a max page size based on the size of our worker threadpool, and ask for messages only after the last known id (if we have one, which we won’t on first run). Then we use write_with_handler to send this request to the server and wait for a reply. The reply is sent after all messages have been sent down (sent seperately, not returned in this reply, see below), but we may still be processing some of them in the worker pool so we next create a helper to wait for the worker pool to be done:

def handle_rsm_reply_when_idle(fin)
	unless EM.defers_finished?
		EM.add_timer(0.1) { handle_rsm_reply_when_idle(fin) }
		return
	end

	last = fin.find_first(
		"./ns:set/ns:last",
		ns: "http://jabber.org/protocol/rsm"
	)&.content

	if last
		DB.execute(<<~SQL, [last, last])
			INSERT INTO data VALUES ('last_mam_id', ?)
			ON CONFLICT(key) DO UPDATE SET value=? WHERE key='last_mam_id'
		SQL
	end
	return if fin["complete"].to_s == "true"

	sync_mam(last)
end

Poll with a timer until the worker pool is all done so that we aren’t fetching new pages before we have handled the last one.  Get the value of the last archive ID that was part of the page just processed and store it in the database for next time we start up.  If this was the last page (that is, complete="true") then we’re all done, otherwise get the next page.  We need to make sure we actually start this sync process inside the when_ready handler:

last_mam_id = DB.execute(<<~SQL)[0]&.first
	SELECT value FROM data WHERE key='last_mam_id' LIMIT 1
SQL
sync_mam(last_mam_id)

And also, we need to actually handle the messages as they come down from the server archive:

message "./ns:result", ns: "urn:xmpp:mam:2" do |_, result|
	fwd = result.xpath("./ns:forwarded", ns: "urn:xmpp:forward:0").first
	fwd = fwd.find_first("./ns:message", ns: "jabber:client")
	m = Blather::XMPPNode.import(fwd)
	next unless m.is_a?(Blather::Stanza::Message) && m.body.present?

	mam_id = result.first["id"]&.to_s
	# Can't really race because we're checking for something from the past
	# Any new message inserted isn't the one we're looking for here anyway
	sent = DB.execute(<<~SQL, [m.id])[0][0]
		SELECT count(*) FROM messages WHERE stanza_id=? AND mam_id IS NULL
	SQL
	if sent < 1
		counterpart = if m.from.stripped.to_s == jid.stripped.to_s
			m.to.stripped.to_s
		else
			m.from.stripped.to_s
		end
		delay =
			fwd.find_first("./ns:delay", ns: "urn:xmpp:delay")
			&.[]("stamp")&.then(Time.method(:parse))
		delay = delay&.to_i || m.delay&.stamp&.to_i || Time.now.to_i
		DB.execute(<<~SQL, [mam_id, m.id, counterpart, delay, m.to_s])
			INSERT OR IGNORE INTO messages
			(mam_id, stanza_id, conversation, created_at, stanza)
			VALUES (?,?,?,?,?)
		SQL
	else
		DB.execute(<<~SQL, [mam_id, m.id])
			UPDATE messages SET mam_id=? WHERE stanza_id=?
		SQL
	end
end

Any message which contains a MAM (server archive) result will get handled here.  Just like with carbons we extract the forwarded message and import, making sure it ends up as a Blather Message object with a body.

Remember how when we stored a sent message we didn’t know the archive ID yet?  Here we check if there is anything in our database already with this stanza ID and no archive ID, if no we will insert it as a new message, but otherwise we can update the row we already have to store the server archive ID on it, which we now know.

And with that, our client now stores and syncs all history with the server, to give the user a full view of their conversation no matter where or when it happened.

Display Names

If a user is added to the contact list with a name, we already show that name instead of their address in conversations.  What if a user is not a contact yet, or we haven’t set a name for them?  It might be useful to be able to fetch any display name they advertise for themselves and show that.  First we add a simple helper to expose write_with_handler outside of the main object:

public def write_with_handler(stanza, &block)
	client.write_with_handler(stanza, &block)
end

We need an attribute on the Conversation to hold the nickname:

attr_accessor :nickname

And then we can use this in Conversation#initialize to fetch the other side’s nickname if they advertise one and we don’t have one for them yet:

self.nickname = BLATHER.my_roster[jid]&.name || jid
return unless nickname.to_s == jid.to_s

BLATHER.write_with_handler(
	Blather::Stanza::PubSub::Items.new(:get).tap { |iq|
		iq.node = "http://jabber.org/protocol/nick"
		iq.to = jid
	}
) do |reply|
	self.nickname = reply.items.first.payload_node.text rescue self.nickname
end

Inside the window declaration we can use this as the window title:

title <=> [self, :nickname]

and in format_sender we can use this as well:

return nickname if jid.to_s == @jid.to_s

Avatars

Names are nice, but what about pictures?  Can we have nice avatar images that go with each user?  What should we display if they don’t have an avatar set?  Well not only is there a protocol to get an avatar, but a specification that allows all clients to use the same colours to represent things, so we can use a block of that if there is no avatar set.  Let’s generate the colour blocks first.  Add this to Gemfile:

gem "hsluv"

Require the library at the top:

require "hsluv"

$avatars = {}

And a method on Conversation to use this:

def default_avatar(string)
	hue = (Digest::SHA1.digest(string).unpack1("v").to_f / 65536) * 360
	rgb = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(hue, 100, 50))
	rgba = rgb.pack("CCC") + "xff".b
	image { image_part(rgba * 32 * 32, 32, 32, 4) }
end

This takes the SHA-1 of a string, unpacks the first two bytes as a 16-bit little-endian integer, converts the range from 0 to MAX_SHORT into the range from 0 to 360 for hue degrees, then passes to the library we added to convert from HSV to RGB colour formats.  The GUI library expects images as a byte string where every 4 bytes are 0 to 255 for red, then green, then blue, then transparency.  Because we want a square of all one colour, we can create the byte string for one pixel and then multiply the string by the width and height (multiplying a string by a number in Ruby make a new string with that many copies repeated) to get the whole image.

In Conversation#initialize we can use this to make a default avatar on the dummy message row then the window first opens:

@messages = [[default_avatar(""), "", ""]]

And we will need to add a new column definition to be beginning of the table { block:

image_column("Avatar")

And actually add the image to message_row:

def message_row(m)
	from = m.from&.stripped || BLATHER.jid.stripped
	[
		$avatars[from.to_s] || default_avatar(from.to_s),
		format_sender(from),
		m.body
	]
end

If you run this you should now see a coloured square next to each message.  We would now like to get actual avatars, so add this somewhere at the top level to advertise support for this:

set_caps(
	"https://git.singpolyma.net/jabber-client-demo",
	[],
	["urn:xmpp:avatar:metadata+notify"]
)

Then in the when_ready block make sure to send it to the server:

send_caps

And handle the avatars as they come in:

pubsub_event(
	"//ns:items[@node='urn:xmpp:avatar:metadata']",
	ns: "http://jabber.org/protocol/pubsub#event"
) do |m|
	id = m.items.first&.payload_node&.children&.first&.[]("id")
	next $avatars.delete(m.from.stripped.to_s) unless id

	path = DATA_DIR + id.to_s
	key = m.from.stripped.to_s
	if path.exist?
		LibUI.queue_main { $avatars[key] = image(path.to_s, 32, 32) rescue nil }
	else
		write_with_handler(
			Blather::Stanza::PubSub::Items.new(:get).tap { |iq|
				iq.node = "urn:xmpp:avatar:data"
				iq.to = m.from
			}
		) do |reply|
			next if reply.error?

			data = Base64.decode64(reply.items.first&.payload_node&.text.to_s)
			path.write(data)
			LibUI.queue_main { $avatars[key] = image(path.to_s, 32, 32) rescue nil }
		end
	end
end

When an avatar metadata event comes in, we check what it is advertising as the ID of the avatar for this user.  If there is none, that means they don’t have an avatar anymore so delete anything we may have in the global cache for them, otherwise create a file path in the same folder as the database based on this ID.  If that file exists already, then no need to fetch it again, create the image from that path on the GUI thread and set it into our global in-memory cache.  If the file does not exist, then use write_with_handler to request their avatar data.  It comes back Base64 encoded, so decode it and then write it to the file.

If you run this you should now see avatars next to messages for anyone who has one set.

Delivery Receipts

The Internet is a wild place, and sometimes things don’t work out how you’d hope.  Sometimes something goes wrong, or perhaps just all of a user’s devices are turned off.  Whatever the reason, it can be useful to see if a message has been delivered to at least one of the intended user’s devices yet or not.  We’ll need a new database column to store that status, add after the end of the DB.user_version < 1 if block:

if DB.user_version < 2
	DB.execute(<<~SQL)
		ALTER TABLE messages ADD COLUMN delivered INTEGER NOT NULL DEFAULT 0
	SQL
	DB.user_version = 2
end

Let’s advertise support for the feature:

set_caps(
	"https://git.singpolyma.net/jabber-client-demo",
	[],
	["urn:xmpp:avatar:metadata+notify", "urn:xmpp:receipts"]
)

We need to add delivery status and stanza id to the dummy row for the messages table:

@messages = [[default_avatar(""), "", "", false, nil]]

And make sure we select the status out of the database when loading up messages:

SELECT stanza,delivered FROM messages WHERE conversation=? ORDER BY created_at

And pass that through when building the message rows

mam_messages << [m, row[1]]

…

mam_messages.map! { |args| message_row(*args) }

Update the messages table to expect the new data model:

table {
	image_column("Avatar")
	text_column("Sender")
	text_column("Message")
	checkbox_column("Delivered")
	editable false
	cell_rows @messages
	@messages.clear if @messages.length == 1 && @messages.first.last.nil?
}

And update the row builder to include this new data:

def message_row(m, delivered=false)
	from = m.from&.stripped || BLATHER.jid.stripped
	[
		$avatars[from.to_s] || default_avatar(from.to_s),
		format_sender(from),
		m.body,
		delivered,
		m.id
	]
end

Inbound messages are always considered delivered, since we have them:

def new_message(m)
	@messages << message_row(m, true)
end

And a method to allow signalling that a delivery receipt should be displayed, using the fact that we now hide the stanza id off the end of the rows in the table to find the relevant message to update:

def delivered_message(id)
	row = @messages.find_index { |r| r.last == id }
	return unless row

	@messages[row] = @messages[row][0..-3] + [true, id]
end

In the Send button’s on_clicked handler we need to actually request that others send us receipts:

m = message
xml_child(m, :request, "urn:xmpp:receipts")

And we need to handle the receipts when they arrive:

message "./ns:received", ns: "urn:xmpp:receipts" do |m, received|
	DB.execute(<<~SQL, [received.first["id"].to_s])
		UPDATE messages SET delivered=1 WHERE stanza_id=?
	SQL

	conversation = $conversations[m.from.stripped.to_s]
	return unless conversation

	LibUI.queue_main do
		conversation.delivered_message(received.first["id"].to_s)
	end
end

When we get a received receipt, we get the id attribute off of it, which represents a stanza ID that this receipt is for.  We update the database, and inform any open conversation window so the GUI can be updated.

Finally, if someone requests a receipt from us we should send it to them:

message :body do |m|
	handle_live_message(m)

	if m.id && m.at("./ns:request", ns: "urn:xmpp:receipts")
		self << m.reply(remove_children: true).tap { |receipt|
			xml_child(receipt, :received, "urn:xmpp:receipts").tap { |received|
				received["id"] = m.id
			}
		}
	end
end

If the stanza has an id and a receipt request, we construct a reply that contains just the received receipt and send it.

Message Correction

Sometimes people send a message with a mistake in it and want to send another to fix it.  It is convenvient for the GUI to support this and render only the new version of the message.  So let’s implement that.  First we add it to the list of things we advertise support for:

set_caps(
	"https://git.singpolyma.net/jabber-client-demo",
	[],
	[
		"urn:xmpp:avatar:metadata+notify",
		"urn:xmpp:receipts",
		"urn:xmpp:message-correct:0"
	]
)

Then we need a method on Conversation to process incoming corrections and update the GUI:

def new_correction(replace_id, m)
	row = @messages.find_index { |r| r.last == replace_id }
	return unless row

	@messages[row] = message_row(m, true)
end

We look up the message row on the stanza id, just as we did for delivery receipts, and just completely replace it with a row based on the new incoming message.  That’s it for the GUI.  Corrections may come from live messages, from carbons, or even from the server archive if they happened while we were disconnected, so we create a new insert_message helper to handle any case we previously did the SQL INSERT for an incoming message:

def insert_message(
	m,
	mam_id:,
	counterpart: m.from.stripped.to_s,
	delay: m.delay&.stamp&.to_i
)
	if (replace = m.at("./ns:replace", ns: "urn:xmpp:message-correct:0"))
		DB.execute(<<~SQL, [m.to_s, counterpart, replace["id"].to_s])
			UPDATE messages SET stanza=? WHERE conversation=? AND stanza_id=?
		SQL
	else
		delay ||= Time.now.to_i
		DB.execute(<<~SQL, [mam_id, m.id, counterpart, delay, m.to_s])
			INSERT OR IGNORE INTO messages
			(mam_id, stanza_id, conversation, created_at, stanza, delivered)
			VALUES (?,?,?,?,?,1)
		SQL
	end
end

The else case here is the same as the INSERTs we’ve been using up to this point, but we also check first for an element that signals this as a replacement and if that is the case we issue an UPDATE instead to correct our internal archive to the new version.

Then in handle_live_message we also signal the possibly-open GUI:

if (replace = m.at("./ns:replace", ns: "urn:xmpp:message-correct:0"))
	conversation.new_correction(replace["id"].to_s, m)
else
	conversation.new_message(m)
end

We can now display incoming corrections, but it would also be nice to be able to send them.  Add a second button after the Send button in Conversation that can re-use the @message_entry box to correct the most recently sent message:

button("Correct") {
	stretchy false

	on_clicked do
		replace_row = @messages.rindex { |message|
			message[1] == format_sender(BLATHER.jid.stripped)
		}
		next unless replace_row

		m = message
		m << xml_child(m, :replace, "urn:xmpp:message-correct:0").tap { |replace|
			replace["id"] = @messages[replace_row].last
		}
		EM.defer do
			BLATHER << m
			DB.execute(<<~SQL, [m.to_s, @jid, @messages[replace_row].last])
				UPDATE messages SET stanza=? WHERE conversation=? AND stanza_id=?
			SQL
		end
		@messages[replace_row] = message_row(m, @messages[replace_row][-2])
		@message_entry.text = ""
	end
}

When the button is clicked we find the row for the most recently sent message, construct a message to send just as in the Send case but add the message correction replace child with the id matching the stanza id of the most recently sent message.  We send that message and also update our own local copy of the stanza both in the database and in the memory model rendered in the GUI.

Conclusion

There are a lot more features that a chat system can implement, but hopefully this gives you a useful taste of how each one can be incrementally layered in, and what the considerations might be for a wide variety of different kinds of features.  All the code for the working application developed in this article is available in git under AGPLv3+, with commits that corrospond to the path we took here.

Featured Image

Newsletter: New Cheogram Android Release, Chatwoot Instance

singpolyma@singpolyma.net

Hi everyone!

Welcome to the latest edition of your pseudo-monthly JMP update!

In case it’s been a while since you checked out JMP, here’s a refresher: JMP lets you send and receive text and picture messages (and calls) through a real phone number right from your computer, tablet, phone, or anything else that has a Jabber client.  Among other things, JMP has these features: Your phone number on every device; Multiple phone numbers, one app; Free as in Freedom; Share one number with multiple people.

October saw the release of Cheogram Android 2.10.10-3, the largest release in awhile.  The app now stores data de-duplicated, so if you send or receive the same file multiple times only one copy will be stored.  This also lays the groundwork for some new file transfer improvements that will be coming in the future.  The app also now supports displaying rich text messages sent by clients which support that (such as Gajim), including the image protocol needed to display stickers sent by Movim users.  A form of message retraction is also supported, and should work with most Jabber clients out there.  A reminder that message retraction is a social convention and not a security feature – the target still has a full copy of your un-retracted message if they want it.

We know lots of you have big contact lists, across multiple accounts, and that’s why this release introduces the ability to edit tags on your contacts and a tag navigation widget integrated into contact search: to make finding the right conversation just a little bit easier.  We would love to hear feedback about this UI and how well it works for you.

For those of you who start a lot of group texts, there is an easy way to do that built into the app now as well.  When you start a “private group chat” and select only @cheogram.com contacts, you will be prompted to ask if you meant to start a group text instead.  This flow seemed necessary, as many have accidentally created private channels with their SMS contacts instead of the intended group text, so checking at this point was likely to be necessary anyway.

There are also some smaller quality of life improvements in this release, including the ability to copy any link in a message to the clipboard (not just the first one), dumping app logs to a special directory on your device after every call in order to make debugging issues easier, asking if you want to keep app data on uninstall (to make switching back and forth to custom builds possible without always needing export/re-import), a new first-start welcome screen, performance improvements, and more.

As JMP grows so does our support load.  Up until this month we have been managing all our support requests using normal Jabber clients (mostly Gajim and Dino), which worked well enough but less and less well as we grew.  It would be hard to know if someone else was handling a request, who had previously handled a request, or even what the status of some requests were (if they had been resolved elsewhere in the public channel or otherwise).  We’re a small enough team that we can just talk to each other to solve these things, but that does take time, and more time as there are more things to talk out.  So this month we built an XMPP channel integration for Chatwoot and have migrated our main support infrastructure to a Cheogram-hosted instance.  So far we like this a lot, and so much we’ve decided to share.  If you have a project that handles support using Jabber (or SMS with JMP!) you can use it on the Cheogram Chatwoot instance.  Just come by the chatroom and let us know you’re interested.  Only the XMPP channel works on our instance for now, but we’d be happy to enable other channels as well if that would be useful.

And finally, we know many of you are excited about the JMP Data Plan.  Roll out to the waiting list has gone a bit slower than we hoped, but many SIMs did go out in October.  There have been some bumps as you might expect with any test phase, but overall things are looking good and we hope to speed up the roll out and even move on to the next phase soon.

To learn what’s happening with JMP between newsletters, here are some ways you can find out:

Thanks for reading and have a wonderful rest of your week!

Featured Image

SMS Account Verification

singpolyma@singpolyma.net

Some apps and services (but not JMP!) require an SMS verification code in order to create a new account.  (Note that this is different from using SMS for authentication; which is a bad idea since SMS can be easily intercepted, are not encrypted in transit, and are vulnerable to simple swap scams, etc.; but has different incentives and issues.)  Why do they do this, and how can it affect you as a user?

Tarpit

In the fight against service abuse and SPAM, there are no sure-fire one-size-fits-all solutions.  Often preventing abusive accounts and spammers entirely is not possible, so targets turn to other strategies, such as tarpits.  This is anything that slows down the abusive activity, thus resulting in less of it.  This is the best way to think about most account-creation verification measures.  Receiving an SMS to a unique phone number is something that is not hard for most customers creating an account.  Even a customer who does not wish to give out their phone number or does not have a phone number can (in many countries, with enough money) get a new cell phone and cell phone number fairly quickly and use that to create the account.

If a customer is expected to be able to pass this check easily, and an abuser is indistiguishable from a customer, then how can any SMS verification possibly help prevent abuse?  Well, if the abuser needs to create only one account, it cannot.  However, in many cases an abuser is trying to create tens of thousands of accounts.  Now imagine trying to buy ten thousand new cell phones at your local store every day.  It is not going to be easy.

“VoIP Numbers”

Now, JMP can easily get ten thousand new SMS-enabled numbers in a day.  So can almost any other carrier or reseller.  If there is no physical device that needs to be handed over (such as with VoIP, eSIM, and similar services), the natural tarpit is gone and all that is left is the prices and policies of the provider.  JMP has many times received requests to help with getting “10,000 numbers, only need them for one day”.  Of course, we do not serve such customers.  JMP is not here to facilitate abuse, but to help create a gateway to the phone network for human beings whose contacts are still only found there.  That doesn’t mean there are no resellers who will work with such a customer, however.

So now the targets are in a pickle if they want to keep using this strategy.  If the abuser can get ten thousand SMS-enabled numbers a day, and if it doesn’t cost too much, then it won’t work as a tarpit at all!  So many of them have chosen a sort of scorched-earth policy.  They buy and create heuristics to guess if a phone number was “too easy” to get, blocking entire resellers, entire carriers, entire countries.  These rules change daily, are different for every target, and can be quite unpredictable.  This may help when it comes to foiling the abusers, but is bad if you are a customer who just wants to create an account.  Some targets, especially “big” ones, have made the decision to lose some customers (or make their lives much more difficult) in order to slow the abusers down.

De-anonymization

Many apps and services also make money by selling your viewing time to advertisers (e.g. ads interspersed in a social media feed, as pre-/mid-roll in a video, etc.) based on your demographics and behaviour.  To do this, they need to know who you are and what your habits are so they can target the ads you see for the advertisers’ benefit.  As a result, they have an incentive to associate your activity with just one identity, and to make it difficult for you to separate your behaviour in ways that reduce their ability to get a complete picture of who you are.  Some companies might choose to use SMS verification as one of the ways they try to ensure a given person can’t get more than one account, or for associating the account (via the provided phone number) with information they can acquire from other sources, such as where you are at any given time.

Can I make a new account with JMP numbers?

The honest answer is, we cannot say.  While JMP would never work with abusers, and has pricing and incentives set up to cater to long-term users rather than those looking for something “disposable”, communicating that to every app and service out there is a big job.  Many of our customers try to help us with this job by contacting the services they are also customers of; after all, a company is more likely to listen to their own customers than a cold-call from some other company.  The Soprani.ca project has a wiki page where users keep track of what has worked for them, and what hasn’t, so everyone can remain informed of the current state (since a service may work today, but not tomorrow, then work again next week, it is important to track success over time).

Many customers use JMP as their only phone number, often ported in from their previous carrier and already associated with many online accounts.  This often works very well, but everyone’s needs are different.  Especially those creating new personas which start with a JMP number find that creating new accounts at some services for the persona can be frustrating to impossible.  It is an active area of work for us and all other small, easy-access phone network resellers.

Featured Image

Newsletter: Voicemail Changes, Opt-in Jabber ID Discoverability

singpolyma@singpolyma.net

Hi everyone!

Welcome to the latest edition of your pseudo-monthly JMP update!

In case it’s been a while since you checked out JMP, here’s a refresher: JMP lets you send and receive text and picture messages (and calls) through a real phone number right from your computer, tablet, phone, or anything else that has a Jabber client.  Among other things, JMP has these features: Your phone number on every device; Multiple phone numbers, one app; Free as in Freedom; Share one number with multiple people.

This month sees the release of Cheogram Android 2.10.10-2, based on a new upstream version and with many bugfixes and small improvements, especially around the Command UI. We also now have our own F-Droid repositories for quick update of official builds from us. We have a repository for releases and for those who want to help testing new features as they are developed we also have a repository for pre-releases.

Some JMP customers forward their calls to another voicemail service, or otherwise do not have need for the JMP voicemail.  This month we added an official option to the Configure Calls command that allows disabling voicemail completely for users who need this.

The default voicemail outgoing message has been changed from saying “a user of JMP.chat” to specifying what JMP number has been reached.  Anyone with a name or nickname or custom voicemail greeting set is not affected by this change.

As a small improvement for multi-account billing users, renewal transactions now specify what number is being renewed by the transaction.

Cheogram (and thus JMP) is now allowing all users to opt-in to Jabber ID discoverability.  This is to allow users to discover the true Jabber ID behind a phone number so they can upgrade to end-to-end encryption, video calling, high quality media sharing, etc.  This is opt-in only, and most features that make use of this are not built yet, but we wanted to give people the option to express their consent now.  This is done as part of the registration process.  For existing users, if you do not want to opt in, there is nothing you need to do.  If you wish to opt in, simply run the Register command, choose JMP, and it will ask for your consent (it will show if you use the bot as Current Value true for technical reasons, but do not worry it is set to false unless you explicitly answer yes to that question.)

This month we have also made some progress with the early test phase launch of our data-only SIM and eSIM program.  The program is slowly rolling out to the waiting list over the course of the next month, as we gather data and feedback from early users.  If you are interested, adding your Jabber ID to the waiting list is still the best way.  We have also heard the interest in having these available for people who are not otherwise JMP customers, and hope to have that ready for testing soon as well.

To learn what’s happening with JMP between newsletters, here are some ways you can find out:

Thanks for reading and have a wonderful rest of your week!

Featured Image

Privacy and Threat Modelling

singpolyma@singpolyma.net

One often hears people ask if a product or service is “good for privacy” or if some practice they intend to incorporate is “good enough” for their privacy needs.  The problem with most such questions is that they often lack the necessary context, called a threat model, in order to even begin to understand how to answer them.  Understanding your own threat model (and making any implicit model you carry more explicit to yourself) is one of the most important steps you can take to improve your privacy.

What is a Threat Model?

A threat model is a list of possible vulnerabilities, often with attached priorities.  In the context of personal privacy, this includes anyone who you might not want to learn private information about you, what private information you most want that party to remain ignorant of, and why.  For example, someone may not want their ISP to learn that they are communicating on LGBTQ+ forums, because their ISP is their school and their school might tell their parents, whom they are not yet ready to tell.  In this example they might say “I don’t want the school to learn” but because of the reasons it may actually be more important to say “I don’t want my parents to learn.”  So the ISP, the school, and the parents all represent potential vulnerabilities, with the parents as the most important.

Why is a Threat Model Important?

You cannot protect your privacy unless you know what your are protecting and what you are defending against.  Otherwise you may take extra steps to secure something not worth protecting, omit something you were unaware needed protected, or even protect something at the detriment of something you would have cared more about.  Privacy is not a slider from zero to infinity, you cannot be simply “more” or “less” private in some general abstract way.

For example, someone may be a part of a group of insurgents in a small country.  They wish the contents of their communication to be kept a secret from the current government if any one of them is found out, so they choose to use an end-to-end encrypted messaging app.  They have prevented their mobile carrier and government from logging their messages!  They also secure their devices with biometrics so they cannot be stolen.  However, due to the unpopularity of this app in their country, when asked the carrier can immediately identify the current location of anyone using it.  When any of these people are brought in for questioning, the investigator forces the biometric (face or fingerprint) onto the device from the person in custody, unlocks it, gets access to all the decrypted messages, and let’s just say the insurgency is over.

So did the insurgents make “un-private” choices?  No!  For other people with different vulnerabilities, their choices may have been ideal.  But when their identity and current location is more at risk than the content of their messages, sending messages less-encrypted over a more-popular app or protocol (which could have all contents logged for all users, but very likely does not), and deleting them regularly from the local device in case they are caught, would have been more effective.

Privacy LARPing

“Privacy LARPing” is what happens when someone wants to be “more private” because it is cool and not because they have any well-reasoned need for privacy.  Believe it or not, this kind of use case also has a threat model.  The model may be more built on what kinds of vulnerabilities are currently trendy to defend against, but it exists nonetheless.  Putting thought and explicit description into your threat model can be a great way to seem even more “with it” so it’s highly recommended.  You may even identify real threats of concern (there certainly are some for everyone) and move beyond the LARP and into addressing your real needs.

How to Build a Threat Model

This is really an introspection activity.  Ask yourself what kind of entities are most concerning to you.  Estranged friends or lovers?  The other people at the airport or coffee shop?  Local police?  Local SUV owners?  Federal agencies?  Data brokers?  The list of people who may want to know more about you than you want them to is endless, so revisit your model from time to time.  Try to add to it and refine it.  This kind of work is never “done” because the scope is so vast.  Do talk to others and educate yourself about what the set of possible threats is, but do not take each new threat you learn about with the same weight.  Try to understand whether mitigations or new techniques are able to acheieve what you need, rather than blindly applying every “defense” without regard for context.

Signup with Cheogram Android

root@nicolosus.chat

Welcome to JMP.chat! If you are looking for a simple guide on how to sign up for JMP, then you have come to the right place! We will be keeping this guide up-to-date if there is ever a change in how to sign up.

We will first start with signing up from within your Jabber chat application on mobile, where you will never need to leave the client to get set up. I will be using the freedomware Android client Cheogram to do this signup. To start us off, we will need to create a Jabber ID (or “JID”). Upon first opening the app you will be presented with a welcome screen where you can choose to signup using the built-in flow for chatterboxtown.us, or you can choose your own server.

Main Startup Screen Account User Creation Password Creation

We will choose “I need to sign up” for the purposes of this guide, but you are definitely free to choose whatever service you like, or bring your own! On the first screen of the server signup it will ask you to enter a username; this can be anything you want as long as it isn’t already in use on the server. After tapping Next, it will ask you to create a password for this account; length does not seem to be limited so create one as long as you want. Do not forget it, or use a password manager to create/store the password! Tapping Next again will log you in and offer to set an avatar for your account, you can set one now or choose to do so at a later time, if at all.

Captcha Profile Avatar Service Selectioin

Once you are logged in Cheogram will immediately open the sign-up screen with the Cheogram Bot, where you can select which service you are planning to set up. Once you tap on the JMP logo you will be presented with a number search box. Here you can search by Area Code, State (or Province), City/State, Zip Code, or even a vanity pattern by placing the tilde (~) character first followed by up to 4 numbers. You will then be presented with a scrollable list of numbers to choose from, tap on one and then tap next to continue on. On the nex tpage you will choose how to activate your account, with the options of Credit Card, Bitcoin, Referral Code, Mail or e-Transfer. You must also choose which currency you will be using. After selecting one of the four payment options, the next screen will either ask for paymment by the chosen method with details on how to pay, or activate your account if you used a valid referral code.

Number Search Number Search Results Payment Method

Once your account has been activated you will be presented with the command UI of the bot, which has all of your account options, settings and details. Tapping “Show Account Info” will display your phone number, account balance and other useful information. Exiting from this view you should be presented with a popup asking if you want to enable Dialer Integration and grant Microphone permissions, tap yes to enable these features and be taken through the setup of dialer integration.

Command UI Account Info Dialer Integration Permission

Mic Permissions Dialog Calling Accounts Make Calls With Priority

At some point you will also be asked if you want to download the default stickers for use in your chats, if you do tap “YES” and then it will prompt you for Media access permissions (this will allow Cheogram to access labelled folders inside the common media locations such as Downloads, Pictures, Documents, Movies). Tap to allow access permissions and Cheogram will download them in the background. Bonus: you can also download sticker packs from any other site and then load them into Cheogram!

Sticker Download Media Access Dialog Battery Optomization

Now that you have activated your account and set up dialer integration you are able to place calls, even from your native dialer, SMS or MMS with your contacts. To add a contact within Cheogram is quite easy with the contacts integration. Now that the bot has been added to your account contacts, your device’s contacts should already be visible when you tap on the “chat” icon to start a new chat, or you can do the following to add a contact to your Jabber server. First tap the chat icon, tap the “+”, then “add contact”. The first thing you should notice that is different this time with the dialog box that pops up is that it now has two selectable buttons: Jabber ID, and PSTN. The PSTN option makes adding telephone numbers for calling or sending SMS to very easy, just type out the phone number you wish to add to your contacts and then tap CALL or MESAAGE, depending on what you wish to do first. This will automatically format the phone number according to the locale detected on your device. If you need to add an international number, you will need to add the phone in the full international format to override the country code being automatically added. With the contact now added, you can either start typing out a message, or tap the “phone” icon that appears at the top to make an audio call to the contact. Images, videos and audio files can also be sent using a number from JMP.

Contacts Permissions Add Contact 1 Add Contact Form

Featured Image

Newsletter: New Employee, Command UI, JMP SIM Card, Multi-account Billing

singpolyma@singpolyma.net

Hi everyone!

Welcome to the latest edition of your pseudo-monthly JMP update!

In case it’s been a while since you checked out JMP, here’s a refresher: JMP lets you send and receive text and picture messages (and calls) through a real phone number right from your computer, tablet, phone, or anything else that has a Jabber client.  Among other things, JMP has these features: Your phone number on every device; Multiple phone numbers, one app; Free as in Freedom; Share one number with multiple people.

This month sees the addition of a new member to the team, you will see him in the chat as seki.  Seki joins us as a software developer and general team member, be sure to say hi!

Cheogram Android 2.10.9-1 has been released.  This release includes a major new feature: the Command UI.  The best place to see this feature is when talking to the bot at cheogram.com.  You will see a new tab labelled “Commands” that lets you interact with the bot using a nice UI instead of by sending specially-formatted chats.  This release also includes several fixes to URI display and copying, and is based on Conversations 2.10.9 upstream.  We have added a long-press menu on the list of all active conversations to perform quick actions (such as “pin to top”), added support for muting yourself from the dialler integration, changed the ringback sound to be more familiar to USA and Canada users, and various other small bugfixes.

JMP is actively working on providing cost-effective data-only eSIMs and SIM cards for users.  Pricing is not yet final, and there is some work to do before this is ready for the general public, but if you are interested please sign up for the wait list at the link above.  Our first launch will be with USA and Canada coverage, but other areas are possible in the future if there is interest.

This month we are also pleased to announce the launch of multi-account billing.  This feature allows customers to have one account be billed for all their JMP accounts, or those of their family.  To get started with this, please contact support and indicate the accounts that you want linked together.

To learn what’s happening with JMP between newsletters, here are some ways you can find out:

Thanks for reading and have a wonderful rest of your week!

Creative Commons Attribution ShareAlike