Merge pull request #106 from ouchadam/release-candidate

[Auto] Release Candidate
This commit is contained in:
Adam Brown 2022-08-31 20:48:29 +01:00
commit c580bcbdab
44 changed files with 1622 additions and 135 deletions

View File

@ -24,4 +24,6 @@ jobs:
- uses: gradle/gradle-build-action@v2
- name: Assemble debug variant
run: ./gradlew assembleDebug --no-daemon
run: |
./gradlew assembleDebug --no-daemon
./gradlew assembleDebug -Pfoss --no-daemon

31
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Nightly
on:
schedule:
- cron: '0 19 * * *'
jobs:
check-develop-beta-changes:
name: Check if develop is ahead of beta release
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version:
16
- run: npm ci
working-directory: ./tools/beta-release/
- uses: actions/github-script@v6
with:
script: |
const { startReleaseProcess } = await import('${{ github.workspace }}/tools/beta-release/app.js')
await startReleaseProcess({github, context, core})

50
.github/workflows/release-candidate.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: Generate and publish Release Candidate
on:
push:
branches:
- 'release'
jobs:
publish-release-candidate:
name: Publish release candidate
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- uses: actions/setup-node@v3
with:
node-version:
16
- run: npm ci
working-directory: ./tools/beta-release/
- name: Write secrets
run: |
mkdir .secrets
touch .secrets/upload-key.jks
touch .secrets/service-account.json
echo -n '${{ secrets.UPLOAD_KEY }}' | base64 --decode >> .secrets/upload-key.jks
echo -n '${{ secrets.SERVICE_ACCOUNT }}' | base64 --decode >> .secrets/service-account.json
- name: Assemble release variant
run: ./tools/generate-release.sh ${{ secrets.STORE_PASS }}
- uses: actions/github-script@v6
with:
script: |
const { publishRelease } = await import('${{ github.workspace }}/tools/beta-release/app.js')
const artifacts = {
bundle: '${{ github.workspace }}/app/build/outputs/bundle/release/app-release.aab',
mapping: '${{ github.workspace }}/app/build/outputs/mapping/release/mapping.txt',
}
await publishRelease(github, artifacts)

4
.gitignore vendored
View File

@ -10,4 +10,6 @@
.externalNativeBuild
.cxx
local.properties
/benchmark-out
/benchmark-out
**/node_modules
.secrets

View File

@ -16,8 +16,6 @@
- Focused on reliability and stability.
- Bare-bones feature set.
##### _*Google play only with automatic crash reporting enabled_
---
### Feature list
@ -28,7 +26,8 @@
- Message bubbles, supporting text, replies and edits
- Push notifications (DMs always notify, Rooms notify once)
- Importing of E2E room keys from Element clients
- [UnifiedPush](https://unifiedpush.org/)
- [UnifiedPush](https://unifiedpush.org/)
- FOSS variant
### Planned
@ -57,4 +56,34 @@
---
### Building
##### Debug `.apk`
```bash
./gradlew assembleDebug
```
##### Release (signed with debug key) `.apk`
```bash
./gradlew assembleRelease
```
##### Unsigned release `.apk`
```bash
./gradlew assembleRelease -Punsigned
```
##### Unsigned release (FOSS) `.apk`
```bash
./gradlew assembleRelease -Punsigned -Pfoss
```
---
#### Join the conversation @ https://matrix.to/#/#small-talk:iswell.cool

View File

@ -24,6 +24,10 @@ android {
} else {
resConfigs "en"
}
if (isFoss()) {
archivesBaseName = "$archivesBaseName-foss"
}
}
bundle {
@ -46,8 +50,11 @@ android {
"proguard/serializationx.pro",
"proguard/olm.pro"
// actual releases are signed with a different config
signingConfig = buildTypes.debug.signingConfig
if (project.hasProperty("unsigned")) {
// releases are signed externally
} else {
signingConfig = buildTypes.debug.signingConfig
}
}
}
@ -82,6 +89,8 @@ dependencies {
implementation project(":domains:android:imageloader")
implementation project(":domains:olm")
firebase(it, "messaging")
implementation project(":matrix:matrix")
implementation project(":matrix:matrix-http-ktor")
implementation project(":matrix:services:auth")

View File

@ -1,7 +1,6 @@
package app.dapk.st
import android.app.Application
import android.content.Intent
import android.util.Log
import app.dapk.st.core.CoreAndroidModule
import app.dapk.st.core.ModuleProvider
@ -11,13 +10,13 @@ import app.dapk.st.core.extensions.ResettableUnsafeLazy
import app.dapk.st.core.extensions.Scope
import app.dapk.st.directory.DirectoryModule
import app.dapk.st.domain.StoreModule
import app.dapk.st.firebase.messaging.MessagingModule
import app.dapk.st.graph.AppModule
import app.dapk.st.home.HomeModule
import app.dapk.st.login.LoginModule
import app.dapk.st.messenger.MessengerModule
import app.dapk.st.notifications.NotificationsModule
import app.dapk.st.profile.ProfileModule
import app.dapk.st.push.firebase.FirebasePushService
import app.dapk.st.push.PushModule
import app.dapk.st.settings.SettingsModule
import app.dapk.st.share.ShareEntryModule
@ -75,6 +74,7 @@ class SmallTalkApplication : Application(), ModuleProvider {
ProfileModule::class -> featureModules.profileModule
NotificationsModule::class -> featureModules.notificationsModule
PushModule::class -> featureModules.pushModule
MessagingModule::class -> featureModules.messagingModule
MessengerModule::class -> featureModules.messengerModule
TaskRunnerModule::class -> appModule.domainModules.taskRunnerModule
CoreAndroidModule::class -> appModule.coreAndroidModule

View File

@ -16,6 +16,7 @@ import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.directory.DirectoryModule
import app.dapk.st.domain.StoreModule
import app.dapk.st.firebase.messaging.MessagingModule
import app.dapk.st.home.HomeModule
import app.dapk.st.home.MainActivity
import app.dapk.st.imageloader.ImageLoaderModule
@ -55,6 +56,7 @@ import app.dapk.st.olm.OlmPersistenceWrapper
import app.dapk.st.olm.OlmWrapper
import app.dapk.st.profile.ProfileModule
import app.dapk.st.push.PushModule
import app.dapk.st.push.messaging.MessagingServiceAdapter
import app.dapk.st.settings.SettingsModule
import app.dapk.st.share.ShareEntryModule
import app.dapk.st.tracking.TrackingModule
@ -208,6 +210,10 @@ internal class FeatureModules internal constructor(
domainModules.pushModule
}
val messagingModule by unsafeLazy {
domainModules.messaging
}
}
internal class MatrixModules(
@ -428,23 +434,30 @@ internal class DomainModules(
private val dispatchers: CoroutineDispatchers,
) {
val pushModule by unsafeLazy {
val pushHandler by unsafeLazy {
val store = storeModule.value
val pushHandler = MatrixPushHandler(
MatrixPushHandler(
workScheduler = workModule.workScheduler(),
credentialsStore = store.credentialsStore(),
matrixModules.sync,
store.roomStore(),
)
}
val messaging by unsafeLazy { MessagingModule(MessagingServiceAdapter(pushHandler), context) }
val pushModule by unsafeLazy {
PushModule(
errorTracker,
pushHandler,
context,
dispatchers,
SharedPreferencesDelegate(context.applicationContext, fileName = "dapk-user-preferences", dispatchers)
SharedPreferencesDelegate(context.applicationContext, fileName = "dapk-user-preferences", dispatchers),
messaging.messaging,
)
}
val taskRunnerModule by unsafeLazy { TaskRunnerModule(TaskRunnerAdapter(matrixModules.matrix::run, AppTaskRunner(matrixModules.push))) }
}
internal class AndroidImageContentReader(private val contentResolver: ContentResolver) : ImageContentReader {

View File

@ -118,7 +118,7 @@ ext.applyAndroidLibraryModule = { project ->
}
ext.applyCrashlyticsIfRelease = { project ->
if (isReleaseBuild) {
if (isReleaseBuild && !isFoss()) {
project.apply plugin: 'com.google.firebase.crashlytics'
project.afterEvaluate {
project.tasks.withType(com.google.firebase.crashlytics.buildtools.gradle.tasks.UploadMappingFileTask).configureEach {
@ -151,6 +151,19 @@ ext.androidImportFixturesWorkaround = { project, fixtures ->
project.dependencies.testImplementation fixtures.files("build/libs/${fixtures.name}.jar")
}
ext.isFoss = {
return rootProject.hasProperty("foss")
}
ext.firebase = { dependencies, name ->
if (isFoss()) {
dependencies.implementation(project(":domains:firebase:$name-noop"))
} else {
dependencies.implementation(project(":domains:firebase:$name"))
}
}
if (launchTask.contains("codeCoverageReport".toLowerCase())) {
apply from: 'tools/coverage.gradle'
}
}

View File

@ -6,8 +6,9 @@ dependencies {
implementation project(':domains:android:core')
implementation project(':domains:store')
implementation project(':matrix:services:push')
implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-messaging'
firebase(it, "messaging")
implementation Dependencies.mavenCentral.kotlinSerializationJson
implementation Dependencies.jitPack.unifiedPush
}

View File

@ -2,15 +2,6 @@
<manifest package="app.dapk.st.push" xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<service
android:name=".firebase.FirebasePushService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<receiver android:exported="true" android:enabled="true" android:name=".unifiedpush.UnifiedPushMessageReceiver">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>

View File

@ -7,7 +7,8 @@ import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.domain.Preferences
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
import app.dapk.st.push.firebase.FirebasePushTokenRegistrar
import app.dapk.st.firebase.messaging.Messaging
import app.dapk.st.push.messaging.MessagingPushTokenRegistrar
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
class PushModule(
@ -16,15 +17,16 @@ class PushModule(
private val context: Context,
private val dispatchers: CoroutineDispatchers,
private val preferences: Preferences,
private val messaging: Messaging,
) : ProvidableModule {
private val registrars by unsafeLazy {
PushTokenRegistrars(
context,
FirebasePushTokenRegistrar(
MessagingPushTokenRegistrar(
errorTracker,
context,
pushHandler,
messaging,
),
UnifiedPushRegistrar(context),
PushTokenRegistrarPreferences(preferences)

View File

@ -2,7 +2,7 @@ package app.dapk.st.push
import android.content.Context
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
import app.dapk.st.push.firebase.FirebasePushTokenRegistrar
import app.dapk.st.push.messaging.MessagingPushTokenRegistrar
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
import org.unifiedpush.android.connector.UnifiedPush
@ -11,7 +11,7 @@ private val NONE = Registrar("None")
class PushTokenRegistrars(
private val context: Context,
private val firebasePushTokenRegistrar: FirebasePushTokenRegistrar,
private val messagingPushTokenRegistrar: MessagingPushTokenRegistrar,
private val unifiedPushRegistrar: UnifiedPushRegistrar,
private val pushTokenStore: PushTokenRegistrarPreferences,
) : PushTokenRegistrar {
@ -19,27 +19,36 @@ class PushTokenRegistrars(
private var selection: Registrar? = null
fun options(): List<Registrar> {
return listOf(NONE, FIREBASE_OPTION) + UnifiedPush.getDistributors(context).map { Registrar(it) }
val messagingOption = when (messagingPushTokenRegistrar.isAvailable()) {
true -> FIREBASE_OPTION
else -> null
}
return listOfNotNull(NONE, messagingOption) + UnifiedPush.getDistributors(context).map { Registrar(it) }
}
suspend fun currentSelection() = selection ?: (pushTokenStore.currentSelection()?.let { Registrar(it) } ?: FIREBASE_OPTION).also { selection = it }
suspend fun currentSelection() = selection ?: (pushTokenStore.currentSelection()?.let { Registrar(it) } ?: defaultSelection()).also { selection = it }
private fun defaultSelection() = when (messagingPushTokenRegistrar.isAvailable()) {
true -> FIREBASE_OPTION
else -> NONE
}
suspend fun makeSelection(option: Registrar) {
selection = option
pushTokenStore.store(option.id)
when (option) {
NONE -> {
firebasePushTokenRegistrar.unregister()
messagingPushTokenRegistrar.unregister()
unifiedPushRegistrar.unregister()
}
FIREBASE_OPTION -> {
unifiedPushRegistrar.unregister()
firebasePushTokenRegistrar.registerCurrentToken()
messagingPushTokenRegistrar.registerCurrentToken()
}
else -> {
firebasePushTokenRegistrar.unregister()
messagingPushTokenRegistrar.unregister()
unifiedPushRegistrar.registerSelection(option)
}
}
@ -47,7 +56,7 @@ class PushTokenRegistrars(
override suspend fun registerCurrentToken() {
when (selection) {
FIREBASE_OPTION -> firebasePushTokenRegistrar.registerCurrentToken()
FIREBASE_OPTION -> messagingPushTokenRegistrar.registerCurrentToken()
NONE -> {
// do nothing
}
@ -58,10 +67,10 @@ class PushTokenRegistrars(
override fun unregister() {
when (selection) {
FIREBASE_OPTION -> firebasePushTokenRegistrar.unregister()
FIREBASE_OPTION -> messagingPushTokenRegistrar.unregister()
NONE -> {
runCatching {
firebasePushTokenRegistrar.unregister()
messagingPushTokenRegistrar.unregister()
unifiedPushRegistrar.unregister()
}
}

View File

@ -1,18 +0,0 @@
package app.dapk.st.push.firebase
import com.google.firebase.messaging.FirebaseMessaging
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun FirebaseMessaging.token() = suspendCoroutine<String> { continuation ->
this.token.addOnCompleteListener { task ->
when {
task.isSuccessful -> continuation.resume(task.result!!)
task.isCanceled -> continuation.resumeWith(Result.failure(CancelledTokenFetchingException()))
else -> continuation.resumeWith(Result.failure(task.exception ?: UnknownTokenFetchingFailedException()))
}
}
}
private class CancelledTokenFetchingException : Throwable()
private class UnknownTokenFetchingFailedException : Throwable()

View File

@ -1,37 +1,28 @@
package app.dapk.st.push.firebase
package app.dapk.st.push.messaging
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import app.dapk.st.core.AppLogTag
import app.dapk.st.core.extensions.CrashScope
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.log
import app.dapk.st.firebase.messaging.Messaging
import app.dapk.st.push.PushHandler
import app.dapk.st.push.PushTokenPayload
import app.dapk.st.push.PushTokenRegistrar
import app.dapk.st.push.unifiedpush.UnifiedPushMessageReceiver
import com.google.firebase.messaging.FirebaseMessaging
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
class FirebasePushTokenRegistrar(
class MessagingPushTokenRegistrar(
override val errorTracker: ErrorTracker,
private val context: Context,
private val pushHandler: PushHandler,
private val messaging: Messaging,
) : PushTokenRegistrar, CrashScope {
override suspend fun registerCurrentToken() {
log(AppLogTag.PUSH, "FCM - register current token")
context.packageManager.setComponentEnabledSetting(
ComponentName(context, FirebasePushService::class.java),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP,
)
messaging.enable()
kotlin.runCatching {
FirebaseMessaging.getInstance().token().also {
messaging.token().also {
pushHandler.onNewToken(
PushTokenPayload(
token = it,
@ -48,14 +39,10 @@ class FirebasePushTokenRegistrar(
override fun unregister() {
log(AppLogTag.PUSH, "FCM - unregister")
FirebaseMessaging.getInstance().deleteToken()
context.stopService(Intent(context, FirebasePushService::class.java))
context.packageManager.setComponentEnabledSetting(
ComponentName(context, FirebasePushService::class.java),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP,
)
messaging.deleteToken()
messaging.disable()
}
fun isAvailable() = messaging.isAvailable()
}

View File

@ -0,0 +1,31 @@
package app.dapk.st.push.messaging
import app.dapk.st.core.AppLogTag
import app.dapk.st.core.log
import app.dapk.st.firebase.messaging.ServiceDelegate
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.push.PushHandler
import app.dapk.st.push.PushTokenPayload
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
class MessagingServiceAdapter(
private val handler: PushHandler,
) : ServiceDelegate {
override fun onNewToken(token: String) {
log(AppLogTag.PUSH, "FCM onNewToken")
handler.onNewToken(
PushTokenPayload(
token = token,
gatewayUrl = SYGNAL_GATEWAY,
)
)
}
override fun onMessageReceived(eventId: EventId?, roomId: RoomId?) {
log(AppLogTag.PUSH, "FCM onMessage")
handler.onMessageReceived(eventId, roomId)
}
}

View File

@ -2,9 +2,5 @@ applyAndroidLibraryModule(project)
dependencies {
implementation project(':core')
implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-crashlytics'
// is it worth the 400kb size increase?
// implementation 'com.google.firebase:firebase-analytics'
firebase(it, "crashlytics")
}

View File

@ -4,16 +4,12 @@ import android.util.Log
import app.dapk.st.core.AppLogTag
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.log
import com.google.firebase.crashlytics.FirebaseCrashlytics
class CrashlyticsCrashTracker(
private val firebaseCrashlytics: FirebaseCrashlytics,
) : ErrorTracker {
class CrashTrackerLogger : ErrorTracker {
override fun track(throwable: Throwable, extra: String) {
Log.e("ST", throwable.message, throwable)
log(AppLogTag.ERROR_NON_FATAL, "${throwable.message ?: "N/A"} extra=$extra")
firebaseCrashlytics.recordException(throwable)
}
}

View File

@ -1,9 +1,8 @@
package app.dapk.st.tracking
import android.util.Log
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
import com.google.firebase.crashlytics.FirebaseCrashlytics
import app.dapk.st.firebase.crashlytics.CrashlyticsModule
class TrackingModule(
private val isCrashTrackingEnabled: Boolean,
@ -11,13 +10,18 @@ class TrackingModule(
val errorTracker: ErrorTracker by unsafeLazy {
when (isCrashTrackingEnabled) {
true -> CrashlyticsCrashTracker(FirebaseCrashlytics.getInstance())
false -> object : ErrorTracker {
override fun track(throwable: Throwable, extra: String) {
Log.e("error", throwable.message, throwable)
}
}
true -> compositeTracker(
CrashTrackerLogger(),
CrashlyticsModule().errorTracker,
)
false -> CrashTrackerLogger()
}
}
}
private fun compositeTracker(vararg loggers: ErrorTracker) = object : ErrorTracker {
override fun track(throwable: Throwable, extra: String) {
loggers.forEach { it.track(throwable, extra) }
}
}

View File

@ -0,0 +1,5 @@
plugins { id 'kotlin' }
dependencies {
implementation project(':core')
}

View File

@ -0,0 +1,16 @@
package app.dapk.st.firebase.crashlytics
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
class CrashlyticsModule {
val errorTracker: ErrorTracker by unsafeLazy {
object : ErrorTracker {
override fun track(throwable: Throwable, extra: String) {
// no op
}
}
}
}

View File

@ -0,0 +1,7 @@
applyAndroidLibraryModule(project)
dependencies {
implementation project(':core')
implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-crashlytics'
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="app.dapk.st.firebase.crashlytics"/>

View File

@ -0,0 +1,14 @@
package app.dapk.st.firebase.crashlytics
import app.dapk.st.core.extensions.ErrorTracker
import com.google.firebase.crashlytics.FirebaseCrashlytics
class CrashlyticsCrashTracker(
private val firebaseCrashlytics: FirebaseCrashlytics,
) : ErrorTracker {
override fun track(throwable: Throwable, extra: String) {
firebaseCrashlytics.recordException(throwable)
}
}

View File

@ -0,0 +1,13 @@
package app.dapk.st.firebase.crashlytics
import app.dapk.st.core.extensions.ErrorTracker
import app.dapk.st.core.extensions.unsafeLazy
import com.google.firebase.crashlytics.FirebaseCrashlytics
class CrashlyticsModule {
val errorTracker: ErrorTracker by unsafeLazy {
CrashlyticsCrashTracker(FirebaseCrashlytics.getInstance())
}
}

View File

@ -0,0 +1,6 @@
applyAndroidLibraryModule(project)
dependencies {
implementation project(':core')
implementation project(':matrix:common')
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="app.dapk.st.firebase.messaging"/>

View File

@ -0,0 +1,23 @@
package app.dapk.st.firebase.messaging
class Messaging {
fun isAvailable() = false
fun enable() {
// do nothing
}
fun disable() {
// do nothing
}
fun deleteToken() {
// do nothing
}
suspend fun token(): String {
return ""
}
}

View File

@ -0,0 +1,17 @@
package app.dapk.st.firebase.messaging
import android.content.Context
import app.dapk.st.core.ProvidableModule
import app.dapk.st.core.extensions.unsafeLazy
@Suppress("UNUSED")
class MessagingModule(
val serviceDelegate: ServiceDelegate,
val context: Context,
) : ProvidableModule {
val messaging by unsafeLazy {
Messaging()
}
}

View File

@ -0,0 +1,9 @@
package app.dapk.st.firebase.messaging
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
interface ServiceDelegate {
fun onNewToken(token: String)
fun onMessageReceived(eventId: EventId?, roomId: RoomId?)
}

View File

@ -0,0 +1,9 @@
applyAndroidLibraryModule(project)
dependencies {
implementation project(':core')
implementation project(':domains:android:core')
implementation project(':matrix:common')
implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-messaging'
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="app.dapk.st.firebase.messaging" xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<service
android:name=".FirebasePushServiceDelegate"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
</application>
</manifest>

View File

@ -1,4 +1,4 @@
package app.dapk.st.push.firebase
package app.dapk.st.firebase.messaging
import app.dapk.st.core.AppLogTag
import app.dapk.st.core.extensions.unsafeLazy
@ -6,31 +6,21 @@ import app.dapk.st.core.log
import app.dapk.st.core.module
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.push.PushModule
import app.dapk.st.push.PushTokenPayload
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
class FirebasePushServiceDelegate : FirebaseMessagingService() {
class FirebasePushService : FirebaseMessagingService() {
private val handler by unsafeLazy { module<PushModule>().pushHandler() }
private val delegate by unsafeLazy { module<MessagingModule>().serviceDelegate }
override fun onNewToken(token: String) {
log(AppLogTag.PUSH, "FCM onNewToken")
handler.onNewToken(
PushTokenPayload(
token = token,
gatewayUrl = SYGNAL_GATEWAY,
)
)
delegate.onNewToken(token)
}
override fun onMessageReceived(message: RemoteMessage) {
log(AppLogTag.PUSH, "FCM onMessage")
val eventId = message.data["event_id"]?.let { EventId(it) }
val roomId = message.data["room_id"]?.let { RoomId(it) }
handler.onMessageReceived(eventId, roomId)
delegate.onMessageReceived(eventId, roomId)
}
}

View File

@ -0,0 +1,54 @@
package app.dapk.st.firebase.messaging
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailabilityLight
import com.google.firebase.messaging.FirebaseMessaging
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class Messaging(
private val instance: FirebaseMessaging,
private val context: Context,
) {
fun isAvailable() = GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS
fun enable() {
context.packageManager.setComponentEnabledSetting(
ComponentName(context, FirebasePushServiceDelegate::class.java),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP,
)
}
fun disable() {
context.stopService(Intent(context, FirebasePushServiceDelegate::class.java))
context.packageManager.setComponentEnabledSetting(
ComponentName(context, FirebasePushServiceDelegate::class.java),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP,
)
}
fun deleteToken() {
instance.deleteToken()
}
suspend fun token() = suspendCoroutine { continuation ->
instance.token.addOnCompleteListener { task ->
when {
task.isSuccessful -> continuation.resume(task.result!!)
task.isCanceled -> continuation.resumeWith(Result.failure(CancelledTokenFetchingException()))
else -> continuation.resumeWith(Result.failure(task.exception ?: UnknownTokenFetchingFailedException()))
}
}
}
private class CancelledTokenFetchingException : Throwable()
private class UnknownTokenFetchingFailedException : Throwable()
}

View File

@ -0,0 +1,17 @@
package app.dapk.st.firebase.messaging
import android.content.Context
import app.dapk.st.core.ProvidableModule
import app.dapk.st.core.extensions.unsafeLazy
import com.google.firebase.messaging.FirebaseMessaging
class MessagingModule(
val serviceDelegate: ServiceDelegate,
val context: Context,
) : ProvidableModule {
val messaging by unsafeLazy {
Messaging(FirebaseMessaging.getInstance(), context)
}
}

View File

@ -0,0 +1,9 @@
package app.dapk.st.firebase.messaging
import app.dapk.st.matrix.common.EventId
import app.dapk.st.matrix.common.RoomId
interface ServiceDelegate {
fun onNewToken(token: String)
fun onMessageReceived(eventId: EventId?, roomId: RoomId?)
}

View File

@ -34,6 +34,11 @@ include ':domains:store'
include ':domains:olm-stub'
include ':domains:olm'
include ':domains:firebase:crashlytics'
include ':domains:firebase:crashlytics-noop'
include ':domains:firebase:messaging'
include ':domains:firebase:messaging-noop'
include ':matrix:matrix'
include ':matrix:common'
include ':matrix:matrix-http'

152
tools/beta-release/app.js Normal file
View File

@ -0,0 +1,152 @@
import { release } from './release.js'
const config = {
owner: "ouchadam",
repo: "small-talk",
pathToVersionFile: "version.json",
rcBranchesFrom: "main",
rcMergesTo: "release",
packageName: "app.dapk.st"
}
const rcBranchName = "release-candidate"
export const startReleaseProcess = async ({ github, context, core }) => {
console.log("script start")
if (await doesNotHaveInProgressRelease(github) && await isWorkingBranchAhead(github)) {
await startRelease(github)
} else {
console.log(`Release skipped due to being behind`)
}
return ""
}
export const publishRelease = async (github, artifacts) => {
const versionFile = await readVersionFile(github, "release")
await release(
github,
versionFile.content,
config.packageName,
artifacts,
config,
).catch((error) => console.log(error))
}
const isWorkingBranchAhead = async (github) => {
const result = await github.rest.repos.compareCommitsWithBasehead({
owner: config.owner,
repo: config.repo,
basehead: `${config.rcMergesTo}...${config.rcBranchesFrom}`,
per_page: 1,
page: 1,
})
return result.data.status === "ahead"
}
const doesNotHaveInProgressRelease = async (github) => {
const releasePrs = await github.rest.pulls.list({
owner: config.owner,
repo: config.repo,
state: "open",
base: config.rcMergesTo
})
const syncPrs = await github.rest.pulls.list({
owner: config.owner,
repo: config.repo,
state: "open",
base: config.rcBranchesFrom,
head: `${config.owner}:${config.rcMergesTo}`
})
return releasePrs.data.length === 0 && syncPrs.data.length === 0
}
const startRelease = async (github) => {
console.log(`creating release candidate from head of ${config.rcBranchesFrom}`)
await createBranch(github, "release-candidate", config.rcBranchesFrom)
await incrementVersionFile(github, rcBranchName)
const createdPr = await github.rest.pulls.create({
owner: config.owner,
repo: config.repo,
title: "[Auto] Release Candidate",
head: rcBranchName,
base: config.rcMergesTo,
body: "todo",
})
github.graphql(
`
mutation ($pullRequestId: ID!, $mergeMethod: PullRequestMergeMethod!) {
enablePullRequestAutoMerge(input: {
pullRequestId: $pullRequestId,
mergeMethod: $mergeMethod
}) {
pullRequest {
autoMergeRequest {
enabledAt
enabledBy {
login
}
}
}
}
}
`,
{
pullRequestId: createdPr.data.node_id,
mergeMethod: "MERGE"
}
)
}
const createBranch = async (github, branchName, fromBranch) => {
const mainRef = await github.rest.git.getRef({
owner: config.owner,
repo: config.repo,
ref: `heads/${fromBranch}`,
})
await github.rest.git.createRef({
owner: config.owner,
repo: config.repo,
ref: `refs/heads/${branchName}`,
sha: mainRef.data.object.sha,
})
}
const incrementVersionFile = async (github, branchName) => {
const versionFile = await readVersionFile(github, branchName)
const updatedVersionFile = {
...versionFile.content,
code: versionFile.content.code + 1,
}
const encodedContentUpdate = Buffer.from(JSON.stringify(updatedVersionFile, null, 2)).toString('base64')
await github.rest.repos.createOrUpdateFileContents({
owner: config.owner,
repo: config.repo,
content: encodedContentUpdate,
path: config.pathToVersionFile,
sha: versionFile.sha,
branch: branchName,
message: "updating version for release",
})
}
const readVersionFile = async (github, branch) => {
const result = await github.rest.repos.getContent({
owner: config.owner,
repo: config.repo,
path: config.pathToVersionFile,
ref: branch,
})
const content = Buffer.from(result.data.content, result.data.encoding).toString()
return {
content: JSON.parse(content),
sha: result.data.sha,
}
}

767
tools/beta-release/package-lock.json generated Normal file
View File

@ -0,0 +1,767 @@
{
"name": "beta-release",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "beta-release",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@googleapis/androidpublisher": "^3.0.0"
}
},
"node_modules/@googleapis/androidpublisher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@googleapis/androidpublisher/-/androidpublisher-3.0.0.tgz",
"integrity": "sha512-r4JfmLlcu/VI4hObZuQ8RW5OeWRFtOxqE9xU8C2GAp3GTu2ZcemEICu69xy/rchpzMNQY4lrr8WiqUG1LE1L5Q==",
"dependencies": {
"googleapis-common": "^5.0.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/arrify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
"engines": {
"node": ">=8"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bignumber.js": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz",
"integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==",
"engines": {
"node": "*"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/fast-text-encoding": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz",
"integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ=="
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/gaxios": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz",
"integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==",
"dependencies": {
"abort-controller": "^3.0.0",
"extend": "^3.0.2",
"https-proxy-agent": "^5.0.0",
"is-stream": "^2.0.0",
"node-fetch": "^2.6.7"
},
"engines": {
"node": ">=10"
}
},
"node_modules/gcp-metadata": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz",
"integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==",
"dependencies": {
"gaxios": "^4.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/get-intrinsic": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/google-auth-library": {
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz",
"integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==",
"dependencies": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"fast-text-encoding": "^1.0.0",
"gaxios": "^4.0.0",
"gcp-metadata": "^4.2.0",
"gtoken": "^5.0.4",
"jws": "^4.0.0",
"lru-cache": "^6.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/google-p12-pem": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz",
"integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==",
"dependencies": {
"node-forge": "^1.3.1"
},
"bin": {
"gp12-pem": "build/src/bin/gp12-pem.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/googleapis-common": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-5.1.0.tgz",
"integrity": "sha512-RXrif+Gzhq1QAzfjxulbGvAY3FPj8zq/CYcvgjzDbaBNCD6bUl+86I7mUs4DKWHGruuK26ijjR/eDpWIDgNROA==",
"dependencies": {
"extend": "^3.0.2",
"gaxios": "^4.0.0",
"google-auth-library": "^7.14.0",
"qs": "^6.7.0",
"url-template": "^2.0.8",
"uuid": "^8.0.0"
},
"engines": {
"node": ">=10.10.0"
}
},
"node_modules/gtoken": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz",
"integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==",
"dependencies": {
"gaxios": "^4.0.0",
"google-p12-pem": "^3.1.3",
"jws": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/url-template": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
},
"dependencies": {
"@googleapis/androidpublisher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@googleapis/androidpublisher/-/androidpublisher-3.0.0.tgz",
"integrity": "sha512-r4JfmLlcu/VI4hObZuQ8RW5OeWRFtOxqE9xU8C2GAp3GTu2ZcemEICu69xy/rchpzMNQY4lrr8WiqUG1LE1L5Q==",
"requires": {
"googleapis-common": "^5.0.1"
}
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"requires": {
"debug": "4"
}
},
"arrify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bignumber.js": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz",
"integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A=="
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fast-text-encoding": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz",
"integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ=="
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gaxios": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz",
"integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==",
"requires": {
"abort-controller": "^3.0.0",
"extend": "^3.0.2",
"https-proxy-agent": "^5.0.0",
"is-stream": "^2.0.0",
"node-fetch": "^2.6.7"
}
},
"gcp-metadata": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz",
"integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==",
"requires": {
"gaxios": "^4.0.0",
"json-bigint": "^1.0.0"
}
},
"get-intrinsic": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
}
},
"google-auth-library": {
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz",
"integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==",
"requires": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"fast-text-encoding": "^1.0.0",
"gaxios": "^4.0.0",
"gcp-metadata": "^4.2.0",
"gtoken": "^5.0.4",
"jws": "^4.0.0",
"lru-cache": "^6.0.0"
}
},
"google-p12-pem": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz",
"integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==",
"requires": {
"node-forge": "^1.3.1"
}
},
"googleapis-common": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-5.1.0.tgz",
"integrity": "sha512-RXrif+Gzhq1QAzfjxulbGvAY3FPj8zq/CYcvgjzDbaBNCD6bUl+86I7mUs4DKWHGruuK26ijjR/eDpWIDgNROA==",
"requires": {
"extend": "^3.0.2",
"gaxios": "^4.0.0",
"google-auth-library": "^7.14.0",
"qs": "^6.7.0",
"url-template": "^2.0.8",
"uuid": "^8.0.0"
}
},
"gtoken": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz",
"integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==",
"requires": {
"gaxios": "^4.0.0",
"google-p12-pem": "^3.1.3",
"jws": "^4.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"requires": {
"agent-base": "6",
"debug": "4"
}
},
"is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
},
"json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"requires": {
"bignumber.js": "^9.0.0"
}
},
"jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"requires": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
"qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"requires": {
"side-channel": "^1.0.4"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"url-template": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

View File

@ -0,0 +1,11 @@
{
"name": "beta-release",
"version": "1.0.0",
"main": "app.js",
"license": "MIT",
"type": "module",
"private": true,
"dependencies": {
"@googleapis/androidpublisher": "^3.0.0"
}
}

View File

@ -0,0 +1,201 @@
import * as google from '@googleapis/androidpublisher';
import * as fs from "fs";
import * as http from 'https';
import * as url from 'url';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const auth = new google.auth.GoogleAuth({
keyFile: '.secrets/service-account.json',
scopes: ['https://www.googleapis.com/auth/androidpublisher'],
})
const androidPublisher = google.androidpublisher({
version: 'v3',
auth: auth,
})
const universalApkPath = `${__dirname}/universal.apk`
export const release = async (github, version, applicationId, artifacts, config) => {
const appEditId = await startPlayRelease(applicationId)
console.log("Uploading bundle...")
await uploadBundle(appEditId, applicationId, artifacts.bundle)
console.log("Uploading mapping...")
await uploadMappingFile(appEditId, version.code, applicationId, artifacts.mapping)
console.log("Assign artifacts to beta release...")
await addReleaseToTrack(appEditId, version, applicationId)
console.log("Commiting draft release...")
await androidPublisher.edits.commit({
editId: appEditId,
packageName: applicationId,
}).catch((error) => Promise.reject(error.response.data))
console.log("Downloading generated universal apk...")
await dowloadSignedUniversalApk(
version,
applicationId,
await auth.getAccessToken(),
universalApkPath
)
const releaseResult = await github.rest.repos.createRelease({
owner: config.owner,
repo: config.repo,
tag_name: version.name,
prerelease: true,
generate_release_notes: true,
})
console.log(releaseResult.data.id)
await github.rest.repos.uploadReleaseAsset({
owner: config.owner,
repo: config.repo,
release_id: releaseResult.data.id,
name: `universal-${version.name}.apk`,
data: fs.readFileSync(universalApkPath)
})
console.log("Promoting beta draft release to live...")
await promoteDraftToLive(applicationId)
}
const startPlayRelease = async (applicationId) => {
const result = await androidPublisher.edits.insert({
packageName: applicationId
}).catch((error) => Promise.reject(error.response.data))
return result.data.id
}
const uploadBundle = async (appEditId, applicationId, bundleReleaseFile) => {
const res = await androidPublisher.edits.bundles.upload({
packageName: applicationId,
editId: appEditId,
media: {
mimeType: 'application/octet-stream',
body: fs.createReadStream(bundleReleaseFile)
}
}).catch((error) => Promise.reject(error.response.data))
return res.data
}
const uploadMappingFile = async (appEditId, versionCode, applicationId, mappingFilePath) => {
await androidPublisher.edits.deobfuscationfiles.upload({
packageName: applicationId,
editId: appEditId,
apkVersionCode: versionCode,
deobfuscationFileType: 'proguard',
media: {
mimeType: 'application/octet-stream',
body: fs.createReadStream(mappingFilePath)
}
}).catch((error) => Promise.reject(error.response.data))
}
const addReleaseToTrack = async (appEditId, version, applicationId) => {
const result = await androidPublisher.edits.tracks
.update({
editId: appEditId,
packageName: applicationId,
track: "beta",
requestBody: {
track: "beta",
releases: [
{
name: version.name,
status: "draft",
releaseNotes: {
language: "en-GB",
text: "Bug fixes and improvments - See https://github.com/ouchadam/small-talk/releases for more details",
},
versionCodes: [version.code]
}
]
}
})
.catch((error) => Promise.reject(error.response.data))
return result.data;
}
const dowloadSignedUniversalApk = async (version, applicationId, authToken, outputFile) => {
console.log("fetching universal apk")
const apkRes = await androidPublisher.generatedapks.list({
packageName: applicationId,
versionCode: version.code,
})
const apks = apkRes.data.generatedApks
console.log(`found ${apks.length} apks`)
apks.forEach((apk) => {
console.log(apk)
})
const id = apks[0].generatedUniversalApk.downloadId
console.log(`downloading: ${id}`)
const downloadUrl = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${applicationId}/generatedApks/${version.code}/downloads/${id}:download?alt=media`
const options = {
headers: {
"Authorization": `Bearer ${authToken}`
}
}
await downloadToFile(downloadUrl, options, outputFile)
}
const downloadToFile = async (url, options, outputFile) => {
return new Promise((resolve, error) => {
http.get(url, options, (response) => {
const file = fs.createWriteStream(outputFile)
response.pipe(file)
file.on("finish", () => {
file.close()
resolve()
})
file.on("error", (cause) => {
error(cause)
})
}).on("error", (cause) => {
error(cause)
})
})
}
const promoteDraftToLive = async () => {
const fappEditId = await startPlayRelease(applicationId)
await androidPublisher.edits.tracks
.update({
editId: fappEditId,
packageName: applicationId,
track: "beta",
requestBody: {
track: "beta",
releases: [
{
status: "completed",
}
]
}
})
.catch((error) => Promise.reject(error.response.data))
await androidPublisher.edits.commit({
editId: fappEditId,
packageName: applicationId,
}).catch((error) => Promise.reject(error.response.data))
}

View File

@ -67,30 +67,10 @@ def collectProjects(predicate) {
return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
}
//task unitCodeCoverageReport(type: JacocoReport) {
// outputs.upToDateWhen { false }
// rootProject.apply plugin: 'jacoco'
// def excludedProjects = [
// 'olm-stub',
// 'test-harness'
// ]
// def projects = collectProjects { !excludedProjects.contains(it.name) }
// dependsOn { ["app:assembleDebug"] + projects*.test }
// initializeReport(it, projects, excludes)
//}
//
//task harnessCodeCoverageReport(type: JacocoReport) {
// outputs.upToDateWhen { false }
// rootProject.apply plugin: 'jacoco'
// def projects = collectProjects { true }
// dependsOn { ["app:assembleDebug", project(":test-harness").test] }
// initializeReport(it, projects, excludes)
//}
task allCodeCoverageReport(type: JacocoReport) {
outputs.upToDateWhen { false }
rootProject.apply plugin: 'jacoco'
def projects = collectProjects { !it.name.contains("stub") }
def projects = collectProjects { !it.name.contains("stub") && !it.name.contains("-noop") }
dependsOn { ["app:assembleDebug"] + projects*.test }
initializeReport(it, projects, excludes)
}

15
tools/generate-release.sh Executable file
View File

@ -0,0 +1,15 @@
#! /bin/bash
./gradlew clean bundleRelease -Punsigned --no-daemon --no-configuration-cache --no-build-cache
WORKING_DIR=app/build/outputs/bundle/release
RELEASE_AAB=$WORKING_DIR/app-release.aab
cp $RELEASE_AAB $WORKING_DIR/app-release-unsigned.aab
echo "signing $RELEASE_AAB"
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore .secrets/upload-key.jks \
-storepass $1 \
$RELEASE_AAB \
key0

View File

@ -1,4 +1,4 @@
{
"name": "0.0.1-alpha03",
"code": 5
"name": "0.0.1-alpha04",
"code": 7
}