Merge pull request 'Menyelesaikan dasar fungsi plugin' (#1) from pengembangan into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
rizky_denianto committed 2026-09-03 20:21:54 +07:00
commit 1baf543139
22 files changed
+428 -46

No files matched your search

+32 -2
View File
@@ -21,6 +21,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: persiapan needs: persiapan
steps: steps:
- name: Unduh paket untuk membangun citra
run: |
apt update -y
sudo apt install -y libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libudev-dev
apt clean -y
- name: Unduh program - name: Unduh program
uses: actions/checkout@v7 uses: actions/checkout@v7
with: with:
@@ -28,10 +43,25 @@ jobs:
- name: Unduh pnpm - name: Unduh pnpm
uses: pnpm/setup@v2 uses: pnpm/setup@v2
with:
version: 11
- name: Unggah program - name: Penerbitan paket NPM
run: | run: |
pnpm i pnpm i
npm config set @${{ needs.persiapan.outputs.organisasi }}:registry=https://${{ vars.ALAMAT_GITEA }}/api/packages/${{ needs.persiapan.outputs.organisasi }}/npm/ npm config set @${{ needs.persiapan.outputs.organisasi }}:registry=https://${{ vars.ALAMAT_GITEA }}/api/packages/${{ needs.persiapan.outputs.organisasi }}/npm/
npm config set -- '//${{ vars.ALAMAT_GITEA }}/api/packages/${{ needs.persiapan.outputs.organisasi }}/npm/:_authToken' "${{ secrets.GITEA_TOKEN }}" npm config set -- '//${{ vars.ALAMAT_GITEA }}/api/packages/${{ needs.persiapan.outputs.organisasi }}/npm/:_authToken' "${{ secrets.KUNCI_PENERBITAN }}"
npm publish npm publish
rm -rf pnpm-lock.yaml .pnpm-store/
- name: Unduh Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
rustflags: "-A warnings"
- name: Penerbitan peti Cargo
run: |
cargo publish
env:
CARGO_REGISTRIES_GITEA_TOKEN: Bearer ${{ secrets.KUNCI_PENERBITAN }}
+3 -3
View File
@@ -2,7 +2,7 @@
name = "tauri-plugin-auth" name = "tauri-plugin-auth"
version = "0.1.0" version = "0.1.0"
authors = [ "Rizky Denianto" ] authors = [ "Rizky Denianto" ]
description = "" description = "Program sederhana untuk interaksi dengan autentikasi aplikasi tauri di berbagai sistem operasi"
edition = "2024" edition = "2024"
rust-version = "1.98.0" rust-version = "1.98.0"
exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"] exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"]
@@ -18,8 +18,8 @@ strip = "symbols"
debug = false debug = false
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full" } windows = { level = "none" }
linux = { level = "full" } linux = { level = "none" }
macos = { level = "none" } macos = { level = "none" }
android = { level = "full" } android = { level = "full" }
ios = { level = "none" } ios = { level = "none" }
+2
View File
@@ -0,0 +1,2 @@
/build
/.tauri
+48
View File
@@ -0,0 +1,48 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.plugin.auth"
compileSdk = 36
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation(project(":tauri-android"))
implementation("androidx.credentials:credentials:1.7.0-alpha03")
implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
implementation("com.google.android.libraries.identity.googleid:googleid:<latest version>")
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+31
View File
@@ -0,0 +1,31 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
google()
}
resolutionStrategy {
eachPlugin {
switch (requested.id.id) {
case "com.android.library":
useVersion("8.0.2")
break
case "org.jetbrains.kotlin.android":
useVersion("1.8.20")
break
}
}
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()
}
}
include ':tauri-android'
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
+157
View File
@@ -0,0 +1,157 @@
package com.plugin.auth
import android.app.Activity
import android.content.Context
import android.util.Base64
import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.GetCredentialException
import androidx.credentials.exceptions.NoCredentialException
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.security.SecureRandom
@InvokeArg
class KirimArgs {
var idKlien: String? = null
}
@TauriPlugin
class BluclasPlugin(private val activity: Activity) : Plugin(activity) {
private val credentialManager by lazy { CredentialManager.create(activity) }
private val scope = CoroutineScope(Dispatchers.Main)
private val idKlien: String? = null
@Command
fun masuk(invoke: Invoke) {
val args = invoke.parseArgs(KirimArgs::class.java)
idKlien = args.idKlien
if (idKlien.isNullOrEmpty()) {
invoke.reject("ID Klien belum disediakan")
return
}
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(idKlien)
.setNonce(generateSecureRandomNonce())
.build()
val request = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
scope.launch {
try {
val result = credentialManager.getCredential(
request = request,
context = activity
)
handleSignInResult(result, invoke)
} catch (e: NoCredentialException) {
if (args.filterByAuthorizedAccounts) {
signInWithAllAccounts(idKlien, invoke)
} else {
invoke.reject("Tidak ada kredensial Google yang tersedia: ${e.message}")
}
} catch (e: GetCredentialException) {
invoke.reject("Gagal masuk: ${e.message}")
}
}
}
@Command
fun keluar(invoke: Invoke) {
scope.launch {
try {
credentialManager.clearCredentialState(ClearCredentialStateRequest())
invoke.resolve()
} catch (e: ClearCredentialException) {
invoke.reject("Sign-out failed: ${e.message}")
}
}
}
private suspend fun signInWithAllAccounts(serverClientId: String, invoke: Invoke) {
val googleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(serverClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
try {
val result = credentialManager.getCredential(
request = request,
context = activity
)
handleSignInResult(result, invoke)
} catch (e: GetCredentialException) {
invoke.reject("Sign-in failed: ${e.message}")
}
}
private fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom().nextBytes(randomBytes)
return Base64.encodeToString(randomBytes, Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING)
}
private fun handleSignInResult(result: GetCredentialResponse, invoke: Invoke) {
val credential = result.credential
when (credential) {
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
try {
val googleIdTokenCredential =
GoogleIdTokenCredential.createFrom(credential.data)
// NOTE: do not treat the user as signed in on the client
// alone — send idToken to your backend and verify it there
// before trusting it.
val ret = JSObject()
ret.put("idToken", googleIdTokenCredential.idToken)
ret.put("id", googleIdTokenCredential.id)
ret.put("displayName", googleIdTokenCredential.displayName)
ret.put("givenName", googleIdTokenCredential.givenName)
ret.put("familyName", googleIdTokenCredential.familyName)
ret.put(
"profilePictureUri",
googleIdTokenCredential.profilePictureUri?.toString()
)
ret.put("phoneNumber", googleIdTokenCredential.phoneNumber)
invoke.resolve(ret)
} catch (e: GoogleIdTokenParsingException) {
invoke.reject("Invalid Google ID token response: ${e.message}")
}
} else {
invoke.reject("Unexpected credential type: ${credential.type}")
}
}
else -> {
invoke.reject("Unexpected credential type")
}
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
const COMMANDS: &[&str] = &["autentikasi"]; const COMMANDS: &[&str] = &["masuk", "keluar"];
fn main() { fn main() {
tauri_plugin::Builder::new(COMMANDS) tauri_plugin::Builder::new(COMMANDS)
+11 -3
View File
@@ -1,5 +1,13 @@
import { invoke } from '@tauri-apps/api/core' import { invoke } from "@tauri-apps/api/core";
export async function autentikasi(): Promise<[null]> { export type SAuthMuatanMasuk = {
return await invoke<[null]>(""); "id-klien": string;
};
export async function masuk(muatan: SAuthMuatanMasuk): Promise<null> {
return await invoke<null>("plugin:auth|masuk", { payload: muatan });
}
export async function keluar(): Promise<null> {
return await invoke<null>("plugin:auth|masuk");
} }
+2 -2
View File
@@ -1,8 +1,8 @@
{ {
"name": "tauri-plugin-auth-api", "name": "@segubi/tauri-plugin-auth-api",
"version": "0.1.0", "version": "0.1.0",
"author": "Rizky Denianto", "author": "Rizky Denianto",
"description": "", "description": "Program sederhana untuk interaksi dengan autentikasi aplikasi tauri di berbagai sistem operasi",
"type": "module", "type": "module",
"types": "./dist-js/index.d.ts", "types": "./dist-js/index.d.ts",
"main": "./dist-js/index.cjs", "main": "./dist-js/index.cjs",
@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-autentikasi"
description = "Enables the autentikasi command without any pre-configured scope."
commands.allow = ["autentikasi"]
[[permission]]
identifier = "deny-autentikasi"
description = "Denies the autentikasi command without any pre-configured scope."
commands.deny = ["autentikasi"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-keluar"
description = "Enables the keluar command without any pre-configured scope."
commands.allow = ["keluar"]
[[permission]]
identifier = "deny-keluar"
description = "Denies the keluar command without any pre-configured scope."
commands.deny = ["keluar"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-masuk"
description = "Enables the masuk command without any pre-configured scope."
commands.allow = ["masuk"]
[[permission]]
identifier = "deny-masuk"
description = "Denies the masuk command without any pre-configured scope."
commands.deny = ["masuk"]
+32 -5
View File
@@ -4,7 +4,8 @@ Default permissions for the plugin
#### This default permission set includes the following: #### This default permission set includes the following:
- `allow-autentikasi` - `allow-masuk`
- `allow-keluar`
## Permission Table ## Permission Table
@@ -18,12 +19,12 @@ Default permissions for the plugin
<tr> <tr>
<td> <td>
`auth:allow-autentikasi` `auth:allow-keluar`
</td> </td>
<td> <td>
Enables the autentikasi command without any pre-configured scope. Enables the keluar command without any pre-configured scope.
</td> </td>
</tr> </tr>
@@ -31,12 +32,38 @@ Enables the autentikasi command without any pre-configured scope.
<tr> <tr>
<td> <td>
`auth:deny-autentikasi` `auth:deny-keluar`
</td> </td>
<td> <td>
Denies the autentikasi command without any pre-configured scope. Denies the keluar command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`auth:allow-masuk`
</td>
<td>
Enables the masuk command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`auth:deny-masuk`
</td>
<td>
Denies the masuk command without any pre-configured scope.
</td> </td>
</tr> </tr>
+4 -1
View File
@@ -1,3 +1,6 @@
[default] [default]
description = "Default permissions for the plugin" description = "Default permissions for the plugin"
permissions = ["allow-autentikasi"] permissions = [
"allow-masuk",
"allow-keluar"
]
+20 -8
View File
@@ -295,22 +295,34 @@
"type": "string", "type": "string",
"oneOf": [ "oneOf": [
{ {
"description": "Enables the autentikasi command without any pre-configured scope.", "description": "Enables the keluar command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-autentikasi", "const": "allow-keluar",
"markdownDescription": "Enables the autentikasi command without any pre-configured scope." "markdownDescription": "Enables the keluar command without any pre-configured scope."
}, },
{ {
"description": "Denies the autentikasi command without any pre-configured scope.", "description": "Denies the keluar command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-autentikasi", "const": "deny-keluar",
"markdownDescription": "Denies the autentikasi command without any pre-configured scope." "markdownDescription": "Denies the keluar command without any pre-configured scope."
}, },
{ {
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-autentikasi`", "description": "Enables the masuk command without any pre-configured scope.",
"type": "string",
"const": "allow-masuk",
"markdownDescription": "Enables the masuk command without any pre-configured scope."
},
{
"description": "Denies the masuk command without any pre-configured scope.",
"type": "string",
"const": "deny-masuk",
"markdownDescription": "Denies the masuk command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-masuk`\n- `allow-keluar`",
"type": "string", "type": "string",
"const": "default", "const": "default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-autentikasi`" "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-masuk`\n- `allow-keluar`"
} }
] ]
} }
+8 -2
View File
@@ -2,8 +2,14 @@ use tauri::{AppHandle, Runtime, command};
use crate::AuthExt; use crate::AuthExt;
use crate::Result; use crate::Result;
use crate::models::SAuthMuatanMasuk;
#[command] #[command]
pub(crate) async fn autentikasi<R: Runtime>(app: AppHandle<R>) -> Result<Vec<()>> { pub(crate) async fn masuk<R: Runtime>(app: AppHandle<R>, payload: SAuthMuatanMasuk) -> Result<()> {
return app.auth().autentikasi(); return app.auth().masuk(payload);
}
#[command]
pub(crate) async fn keluar<R: Runtime>(app: AppHandle<R>) -> Result<()> {
return app.auth().keluar();
} }
+8 -2
View File
@@ -1,6 +1,8 @@
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tauri::{AppHandle, Runtime, plugin::PluginApi}; use tauri::{AppHandle, Runtime, plugin::PluginApi};
use crate::models::SAuthMuatanMasuk;
pub fn init<R: Runtime, C: DeserializeOwned>( pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>, app: &AppHandle<R>,
_api: PluginApi<R, C>, _api: PluginApi<R, C>,
@@ -12,7 +14,11 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
pub struct Auth<R: Runtime>(AppHandle<R>); pub struct Auth<R: Runtime>(AppHandle<R>);
impl<R: Runtime> Auth<R> { impl<R: Runtime> Auth<R> {
pub fn autentikasi(&self) -> crate::Result<Vec<()>> { pub fn masuk(&self, muatan: SAuthMuatanMasuk) -> crate::Result<()> {
return Ok(vec![]); return Ok(());
}
pub fn keluar(&self) -> crate::Result<()> {
return Ok(());
} }
} }
+3 -1
View File
@@ -19,6 +19,8 @@ use desktop::Auth;
#[cfg(mobile)] #[cfg(mobile)]
use mobile::Auth; use mobile::Auth;
use crate::commands::{keluar, masuk};
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the auth APIs. /// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the auth APIs.
pub trait AuthExt<R: Runtime> { pub trait AuthExt<R: Runtime> {
fn auth(&self) -> &Auth<R>; fn auth(&self) -> &Auth<R>;
@@ -33,7 +35,7 @@ impl<R: Runtime, T: Manager<R>> crate::AuthExt<R> for T {
/// Initializes the plugin. /// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("auth") Builder::new("auth")
.invoke_handler(tauri::generate_handler![commands::autentikasi]) .invoke_handler(tauri::generate_handler![masuk, keluar])
.setup(|app, api| { .setup(|app, api| {
#[cfg(mobile)] #[cfg(mobile)]
let auth = mobile::init(app, api)?; let auth = mobile::init(app, api)?;
+10 -3
View File
@@ -15,7 +15,7 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
api: PluginApi<R, C>, api: PluginApi<R, C>,
) -> crate::Result<Auth<R>> { ) -> crate::Result<Auth<R>> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
let handle = api.register_android_plugin("", "ExamplePlugin")?; let handle = api.register_android_plugin("com.plugin.auth", "AuthPlugin")?;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
let handle = api.register_ios_plugin(init_plugin_auth)?; let handle = api.register_ios_plugin(init_plugin_auth)?;
Ok(Auth(handle)) Ok(Auth(handle))
@@ -25,7 +25,14 @@ pub fn init<R: Runtime, C: DeserializeOwned>(
pub struct Auth<R: Runtime>(PluginHandle<R>); pub struct Auth<R: Runtime>(PluginHandle<R>);
impl<R: Runtime> Auth<R> { impl<R: Runtime> Auth<R> {
pub fn autentikasi(&self) -> crate::Result<Vec<()>> { pub fn masuk(&self, payload: SAuthMuatanMasuk) -> crate::Result<()> {
return self.0.run_mobile_plugin("autentikasi").map_err(Into::into); return self
.0
.run_mobile_plugin("masuk", payload)
.map_err(Into::into);
}
pub fn keluar(&self) -> crate::Result<Vec<()>> {
return self.0.run_mobile_plugin("keluar", ()).map_err(Into::into);
} }
} }
+6
View File
@@ -1 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SAuthMuatanMasuk {
pub id_klien: String,
}