<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>KaiOS &amp;mdash; affe-null</title>
    <link>https://blog.bananahackers.net/affe-null/tag:KaiOS</link>
    <description></description>
    <pubDate>Sun, 03 May 2026 12:52:54 +0200</pubDate>
    <item>
      <title>KaiOS APIs</title>
      <link>https://blog.bananahackers.net/affe-null/kaios-apis</link>
      <description>&lt;![CDATA[You might have already heard that #WhatsApp uses some undocumented APIs that are built into the #KaiOS system. These APIs are powered by the api-daemon, a daemon which lies at a lower level than Gecko. In this post, I will explain what I have found out about this daemon.&#xA;&#xA;The api-daemon is written in #Rust, and provides several &#34;services&#34;. Each service has two parts, the JS client and the Rust library. The Rust library is linked into the api-daemon executable. The JS clients are accessible via HTTP at http://127.0.0.1:8081/api/v1/service/script.js and expose APIs in a libservice variable. A pre-installed version of the daemon is available in /system/kaios, but it is copied to /data/local/service to allow updates without modifying the system partition.&#xA;&#xA;Using the APIs&#xA;&#xA;To demonstrate the usage, let&#39;s create a simple app called &#34;Daemon Test&#34;. We will use the telephony service. Although it is meant to be used to get and set the call state, it seems that real calls have no effect on the reported state. However, the state might reflect ongoing WhatsApp and other VoIP calls.&#xA;&#xA;To use the APIs on KaiOS 2.5, we need the external-api permission:&#xA;&#xA;manifest.webapp&#xA;{&#xA;  &#34;name&#34;: &#34;Daemon Test&#34;,&#xA;  &#34;launchpath&#34;: &#34;/index.html&#34;,&#xA;  &#34;description&#34;: &#34;An app to demonstrate api-daemon functionality&#34;,&#xA;  &#34;type&#34;: &#34;privileged&#34;,&#xA;  &#34;permissions&#34;: {&#xA;    &#34;external-api&#34;: {}&#xA;  }&#xA;}&#xA;&#xA;index.html&#xA;!DOCTYPE html&#xA;html&#xA;  body&#xA;    div id=&#34;callstate&#34;Loading.../div&#xA;    script src=&#34;http://127.0.0.1:8081/api/v1/shared/core.js&#34;/script&#xA;    script src=&#34;http://127.0.0.1:8081/api/v1/shared/session.js&#34;/script&#xA;    script src=&#34;http://127.0.0.1:8081/api/v1/telephony/service.js&#34;/script&#xA;    script src=&#34;daemon-test.js&#34;/script&#xA;  /body&#xA;/html&#xA;&#xA;We can now use these APIs as described in https://developer.stage.kaiostech.com/docs/sfp-3.0/09.migration-from-2.5/next-new-apis/daemon-api/telephony/daemon-api-telephony/, except that we will need to replace localhost with localhost:8081. Note that the documentation is for KaiOS 3.0, but on 2.5 only the telephony, libsignal (for WhatsApp) and tcpsocket services are available. You can also look at the api-daemon source code, which is also only for KaiOS 3.0.&#xA;&#xA;First, we need to set up a sess variable that stores the api-daemon session:&#xA;var sess = new libsession.Session();&#xA;Then, we write some handlers that get called when the session connects/disconnects:&#xA;function onsessionconnected(){&#xA;  libtelephony.TelephonyService.get(sess).then(function(service){&#xA;    / handle session connect /&#xA;  });&#xA;}&#xA;function onsessiondisconnected(){&#xA;  / handle session disconnect /&#xA;}&#xA;&#xA;And after that we try connecting.&#xA;sess.open(&#39;websocket&#39;, &#39;localhost:8081&#39;, &#39;secrettoken&#39;,&#xA;  {onsessionconnected, onsessiondisconnected}, true);&#xA;&#xA;Here is the final code:&#xA;daemon-test.js&#xA;window.onerror = function(e){&#xA;  // Error handling&#xA;  alert(e);&#xA;}&#xA;&#xA;var sess = new libsession.Session();&#xA;var elstate = document.getElementById(&#39;callstate&#39;);&#xA;var toggleCallState = null;&#xA;&#xA;function callStateChangeCallback(state){&#xA;  elstate.textContent = state;&#xA;}&#xA;&#xA;// Set up session callbacks&#xA;var onsessionconnected = function(){&#xA;  alert(&#39;Session connected&#39;);&#xA;  // Get an instance of the telephony manager&#xA;  libtelephony.TelephonyService.get(sess).then(function(manager){&#xA;    alert(&#39;Got Telephony manager&#39;);&#xA;    try {&#xA;      // Show the call state&#xA;      manager.callState.then(function(state){&#xA;        elstate.textContent = &#39;Call state: &#39; + state;&#xA;      });&#xA;&#xA;      // Show it again if it changes&#xA;      manager.addEventListener(manager.CALLSTATECHANGEEVENT, function(state){&#xA;          elstate.textContent = &#39;Call state: &#39; + state;&#xA;      });&#xA;&#xA;      toggleCallState = function(){&#xA;        // Get the call state&#xA;        manager.callState.then(function(state){&#xA;          // Set the call state&#xA;          manager.callState = state ? 0 : 1;&#xA;          alert(state ? &#39;Idle&#39; : &#39;Calling&#39;);&#xA;        });&#xA;      }&#xA;    } catch(e) {&#xA;      alert(e);&#xA;    }&#xA;  }).catch(function(e){&#xA;    alert(&#39;Failed to get Telephony manager: &#39; + JSON.stringify(e));&#xA;    toggleCallState = null;&#xA;  });&#xA;}&#xA;var onsessiondisconnected = function(){&#xA;  alert(&#39;Disconnected&#39;);&#xA;  toggleCallState = null;&#xA;}&#xA;&#xA;// Initialize the api-daemon session&#xA;sess.open(&#39;websocket&#39;, &#39;localhost:8081&#39;, &#39;secrettoken&#39;,&#xA;  {onsessionconnected, onsessiondisconnected}, true);&#xA;&#xA;window.addEventListener(&#39;keydown&#39;, function(e){&#xA;  if(e.key == &#39;Call&#39;){&#xA;    // Toggle the state if &#39;Call&#39; is pressed and the session is ready&#xA;    if(typeof toggleCallState === &#39;function&#39;) toggleCallState();&#xA;  }&#xA;});&#xA;&#xA;There is a lot more to this. I will also make a wiki page, and some more posts about things such as the updater-daemon and remote services.&#xA;&#xA;More tags: #ApiDaemon]]&gt;</description>
      <content:encoded><![CDATA[<p>You might have already heard that <a href="/affe-null/tag:WhatsApp" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WhatsApp</span></a> uses some undocumented APIs that are built into the <a href="/affe-null/tag:KaiOS" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">KaiOS</span></a> system. These APIs are powered by the <code>api-daemon</code>, a daemon which lies at a lower level than Gecko. In this post, I will explain what I have found out about this daemon.</p>

<p>The api-daemon is written in <a href="/affe-null/tag:Rust" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">Rust</span></a>, and provides several “services”. Each service has two parts, the JS client and the Rust library. The Rust library is linked into the api-daemon executable. The JS clients are accessible via HTTP at <code>http://127.0.0.1:8081/api/v1/&lt;service&gt;/&lt;script&gt;.js</code> and expose APIs in a <code>lib_&lt;service&gt;</code> variable. A pre-installed version of the daemon is available in <code>/system/kaios</code>, but it is copied to <code>/data/local/service</code> to allow updates without modifying the system partition.</p>

<h2 id="using-the-apis" id="using-the-apis">Using the APIs</h2>

<p>To demonstrate the usage, let&#39;s create a simple app called “Daemon Test”. We will use the <code>telephony</code> service. Although it is meant to be used to get and set the call state, it seems that real calls have no effect on the reported state. However, the state might reflect ongoing WhatsApp and other VoIP calls.</p>

<p>To use the APIs on KaiOS 2.5, we need the <code>external-api</code> permission:</p>

<p><code>manifest.webapp</code></p>

<pre><code class="language-json">{
  &#34;name&#34;: &#34;Daemon Test&#34;,
  &#34;launch_path&#34;: &#34;/index.html&#34;,
  &#34;description&#34;: &#34;An app to demonstrate api-daemon functionality&#34;,
  &#34;type&#34;: &#34;privileged&#34;,
  &#34;permissions&#34;: {
    &#34;external-api&#34;: {}
  }
}
</code></pre>

<p><code>index.html</code></p>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;div id=&#34;callstate&#34;&gt;Loading...&lt;/div&gt;
    &lt;script src=&#34;http://127.0.0.1:8081/api/v1/shared/core.js&#34;&gt;&lt;/script&gt;
    &lt;script src=&#34;http://127.0.0.1:8081/api/v1/shared/session.js&#34;&gt;&lt;/script&gt;
    &lt;script src=&#34;http://127.0.0.1:8081/api/v1/telephony/service.js&#34;&gt;&lt;/script&gt;
    &lt;script src=&#34;daemon-test.js&#34;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>We can now use these APIs as described in <a href="https://developer.stage.kaiostech.com/docs/sfp-3.0/09.migration-from-2.5/next-new-apis/daemon-api/telephony/daemon-api-telephony/" rel="nofollow">https://developer.stage.kaiostech.com/docs/sfp-3.0/09.migration-from-2.5/next-new-apis/daemon-api/telephony/daemon-api-telephony/</a>, except that we will need to replace <code>localhost</code> with <code>localhost:8081</code>. Note that the documentation is for KaiOS 3.0, but on 2.5 only the <code>telephony</code>, <code>libsignal</code> (for WhatsApp) and <code>tcpsocket</code> services are available. You can also look at the api-daemon <a href="https://github.com/kaiostech/api-daemon" rel="nofollow">source code</a>, which is also only for KaiOS 3.0.</p>

<h4 id="first-we-need-to-set-up-a-sess-variable-that-stores-the-api-daemon-session" id="first-we-need-to-set-up-a-sess-variable-that-stores-the-api-daemon-session">First, we need to set up a <code>sess</code> variable that stores the api-daemon session:</h4>

<pre><code class="language-javascript">var sess = new lib_session.Session();
</code></pre>

<h4 id="then-we-write-some-handlers-that-get-called-when-the-session-connects-disconnects" id="then-we-write-some-handlers-that-get-called-when-the-session-connects-disconnects">Then, we write some handlers that get called when the session connects/disconnects:</h4>

<pre><code class="language-javascript">function onsessionconnected(){
  lib_telephony.TelephonyService.get(sess).then(function(service){
    /* handle session connect */
  });
}
function onsessiondisconnected(){
  /* handle session disconnect */
}
</code></pre>

<h4 id="and-after-that-we-try-connecting" id="and-after-that-we-try-connecting">And after that we try connecting.</h4>

<pre><code class="language-javascript">sess.open(&#39;websocket&#39;, &#39;localhost:8081&#39;, &#39;secrettoken&#39;,
  {onsessionconnected, onsessiondisconnected}, true);
</code></pre>

<h4 id="here-is-the-final-code" id="here-is-the-final-code">Here is the final code:</h4>

<p><code>daemon-test.js</code></p>

<pre><code class="language-javascript">window.onerror = function(e){
  // Error handling
  alert(e);
}

var sess = new lib_session.Session();
var el_state = document.getElementById(&#39;callstate&#39;);
var toggleCallState = null;

function callStateChangeCallback(state){
  el_state.textContent = state;
}

// Set up session callbacks
var onsessionconnected = function(){
  alert(&#39;Session connected&#39;);
  // Get an instance of the telephony manager
  lib_telephony.TelephonyService.get(sess).then(function(manager){
    alert(&#39;Got Telephony manager&#39;);
    try {
      // Show the call state
      manager.callState.then(function(state){
        el_state.textContent = &#39;Call state: &#39; + state;
      });

      // Show it again if it changes
      manager.addEventListener(manager.CALLSTATE_CHANGE_EVENT, function(state){
          el_state.textContent = &#39;Call state: &#39; + state;
      });

      toggleCallState = function(){
        // Get the call state
        manager.callState.then(function(state){
          // Set the call state
          manager.callState = state ? 0 : 1;
          alert(state ? &#39;Idle&#39; : &#39;Calling&#39;);
        });
      }
    } catch(e) {
      alert(e);
    }
  }).catch(function(e){
    alert(&#39;Failed to get Telephony manager: &#39; + JSON.stringify(e));
    toggleCallState = null;
  });
}
var onsessiondisconnected = function(){
  alert(&#39;Disconnected&#39;);
  toggleCallState = null;
}

// Initialize the api-daemon session
sess.open(&#39;websocket&#39;, &#39;localhost:8081&#39;, &#39;secrettoken&#39;,
  {onsessionconnected, onsessiondisconnected}, true);

window.addEventListener(&#39;keydown&#39;, function(e){
  if(e.key == &#39;Call&#39;){
    // Toggle the state if &#39;Call&#39; is pressed and the session is ready
    if(typeof toggleCallState === &#39;function&#39;) toggleCallState();
  }
});
</code></pre>

<p>There is a lot more to this. I will also make a wiki page, and some more posts about things such as the <code>updater-daemon</code> and remote services.</p>

<p>More tags: <a href="/affe-null/tag:ApiDaemon" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">ApiDaemon</span></a></p>
]]></content:encoded>
      <guid>https://blog.bananahackers.net/affe-null/kaios-apis</guid>
      <pubDate>Sat, 08 May 2021 10:20:26 +0200</pubDate>
    </item>
  </channel>
</rss>