Architecture
Spotube uses Cash App's Zipline to run your Kotlin/JS plugin in a sandboxed JavaScript runtime. Understanding the service binding model is essential before implementing any API.
Entry point
Every plugin exports a top-level main() function where services are wired together. The function name is configured in build.gradle.kts:
zipline {
mainFunction.set("com.example.myplugin.main")
}
Inside main(), you connect to the Zipline runtime and register your services. See the js_plugin_example for a complete, working entry point.
Receiving host APIs
Host APIs are services Spotube provides to your plugin - HTTP client, persistent storage, and webview.
private val zipline by lazy { Zipline.get() }
fun main() {
val httpClient = zipline.take<HttpClientAPI>(HttpClientAPI_SERVICE_NAME)
val storage = zipline.take<PersistedStorageAPI>(PersistedStorageAPI_SERVICE_NAME)
}
Each API has a SERVICE_NAME constant (e.g. HttpClientAPI_SERVICE_NAME). For every capability you declare in plugin.json, call take with the corresponding service.
Registering plugin APIs
Plugin APIs are services you provide to Spotube - metadata, audio, lyrics, scrobble. Each gets its own implementation class, registered via bind():
zipline.bind<CoreAPI>(CoreAPI_SERVICE_NAME, RealCoreAPI())
zipline.bind<MetadataSearchAPI>(MetadataSearchAPI_SERVICE_NAME, RealMetadataSearchAPI())
Only bind the APIs your plugin actually implements. Each unused API should simply be omitted - Spotube handles missing services gracefully.
Sharing dependencies
Since each API implementation is a separate class, wrap host APIs and pass them through constructors. Always create a SpotrClient from the raw HttpClientAPI - never pass the host API directly:
CoreAPI - required
Every plugin must implement CoreAPI. It handles plugin identity, authentication, and update checks. The whole source is available in the example's RealCoreAPI.
Authentication flow
When requiresAuthentication is true, Spotube shows a login button. When tapped, login() is called. The typical pattern: open a WebView for the provider's OAuth page, watch the URL for the redirect callback, extract the auth code, exchange for tokens, store them, update loggedInFlow, and close the WebView.
See the full auth flow example in js_plugin_example for the complete implementation.
Service name constants
Every interface has a companion _SERVICE_NAME constant. Import them alongside the interface:
import dev.krtirtho.plugin_interfaces.plugin_apis.core.CoreAPI
import dev.krtirtho.plugin_interfaces.plugin_apis.core.CoreAPI_SERVICE_NAME
The full list of available service constants is imported in the example's main.kt.