安裝
- Add InsForge dependencies to your project
- Maven Central
- GitHub Packages
build.gradle.kts:
repositories {
mavenLocal() // For local development
mavenCentral()
}
dependencies {
implementation("dev.insforge:insforge-kotlin:0.1.6")
}
First, create a GitHub Personal Access Token:
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Select permission:
read:packages
Option A: Environment Variables
Option A: Environment Variables
settings.gradle.kts (or build.gradle.kts):build.gradle.kts:Set environment variables before building:
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin")
credentials {
username = System.getenv("GITHUB_USER") ?: ""
password = System.getenv("GITHUB_TOKEN") ?: ""
}
}
}
dependencies {
implementation("dev.insforge:insforge-kotlin:0.1.6")
}
export GITHUB_USER="your-github-username"
export GITHUB_TOKEN="your-personal-access-token"
Option B: gradle.properties (Local Development)
Option B: gradle.properties (Local Development)
Add credentials to your global Gradle properties file:~/.gradle/gradle.properties:settings.gradle.kts:build.gradle.kts:
gpr.user=your-github-username
gpr.token=ghp_xxxxxxxxxxxx
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin")
credentials {
username = providers.gradleProperty("gpr.user").orNull ?: ""
password = providers.gradleProperty("gpr.token").orNull ?: ""
}
}
}
dependencies {
implementation("dev.insforge:insforge-kotlin:0.1.6")
}
The
~/.gradle/gradle.properties file is stored outside your project, so credentials won’t be accidentally committed to version control.- Initialize InsForge SDK
import dev.insforge.createInsforgeClient
import dev.insforge.auth.Auth
import dev.insforge.database.Database
import dev.insforge.storage.Storage
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.ai.AI
val client = createInsforgeClient(
baseUrl = "https://your-app.insforge.app",
anonKey = "your-api-key"
) {
install(Auth)
install(Database)
install(Storage)
install(Functions)
install(Realtime) {
autoReconnect = true
reconnectDelay = 5000
}
install(AI)
}
- Enable Logging (Optional)
import dev.insforge.InsforgeLogLevel
val client = createInsforgeClient(
baseUrl = "https://your-app.insforge.app",
anonKey = "your-api-key"
) {
// DEBUG: logs request method/URL and response status
// VERBOSE: logs full headers and request/response bodies
logLevel = InsforgeLogLevel.DEBUG
install(Auth)
install(Database)
// ... other modules
}
| Log Level | Description |
|---|---|
NONE | No logging (default, recommended for production) |
ERROR | Only errors |
WARN | Warnings and errors |
INFO | Informational messages |
DEBUG | Debug info (request method, URL, response status) |
VERBOSE | Full details (headers, request/response bodies) |
Use
NONE or ERROR in production to avoid exposing sensitive data in logs.Android Initialization
- Add Chrome Custom Tabs dependency to your
build.gradle.kts:
dependencies {
implementation("androidx.browser:browser:1.9.0")
}
- Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage)
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import dev.insforge.createInsforgeClient
import dev.insforge.ai.AI
import dev.insforge.auth.Auth
import dev.insforge.auth.BrowserLauncher
import dev.insforge.auth.ClientType
import dev.insforge.auth.SessionStorage
import dev.insforge.database.Database
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.storage.Storage
class InsforgeManager(private val context: Context) {
val client = createInsforgeClient(
baseUrl = "https://your-app.insforge.app",
anonKey = "your-anon-key"
) {
install(Auth) {
// 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession
browserLauncher = BrowserLauncher { url ->
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
// Handle non-Activity context safely
if (context is Activity) {
customTabsIntent.launchUrl(context, Uri.parse(url))
} else {
customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
customTabsIntent.launchUrl(context, Uri.parse(url))
}
}
// 2. enable session persistence
persistSession = true
// 3. config SessionStorage (use SharedPreferences)
sessionStorage = object : SessionStorage {
private val prefs = context.getSharedPreferences(
"insforge_auth",
Context.MODE_PRIVATE
)
override suspend fun save(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
override suspend fun get(key: String): String? {
return prefs.getString(key, null)
}
override suspend fun remove(key: String) {
prefs.edit().remove(key).apply()
}
}
// 4. set client type for mobile
clientType = ClientType.MOBILE
}
// Install Database module
install(Database)
// Install Realtime module for real-time subscriptions
install(Realtime) {
debug = true
}
// Install other modules
install(Storage)
install(Functions)
install(AI)
}
}
- Use Jetpack DataStore for Session Storage (Optional)
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = "insforge_auth")
class DataStoreSessionStorage(private val context: Context) : SessionStorage {
override suspend fun save(key: String, value: String) {
context.authDataStore.edit { prefs ->
prefs[stringPreferencesKey(key)] = value
}
}
override suspend fun get(key: String): String? {
return context.authDataStore.data.map { prefs ->
prefs[stringPreferencesKey(key)]
}.first()
}
override suspend fun remove(key: String) {
context.authDataStore.edit { prefs ->
prefs.remove(stringPreferencesKey(key))
}
}
}
// Then use it in your InsForge client
install(Auth) {
browserLauncher = ...
persistSession = true
sessionStorage = DataStoreSessionStorage(context)
}
signUp()
使用電子郵件和密碼建立新使用者帳戶。參數
email(String) - 使用者的電子郵件地址password(String) - 使用者的密碼name(String?, optional) - 使用者的顯示名稱
傳回值
SignUpResponse
SignUpResponse
data class SignUpResponse(
/** User object (null when email verification is required) */
val user: User? = null,
/** Access token (null when email verification is required) */
val accessToken: String? = null,
/** Indicates if email verification is required before sign-in */
val requireEmailVerification: Boolean = false,
/** Redirect URL (if applicable) */
val redirectTo: String? = null,
/** CSRF token (if applicable) */
val csrfToken: String? = null,
/** Refresh token (null when email verification is required) */
val refreshToken: String? = null
)
範例(包含驗證的完整流程)
class AuthViewModel : ViewModel() {
// Sign up and handle verification requirement
suspend fun signUp(email: String, password: String, name: String?) {
try {
val result = client.auth.signUp(
email = email,
password = password,
name = name
)
if (result.requireEmailVerification) {
// Show verification code input screen
// User will receive a 6-digit code via email
_uiState.value = AuthUiState.RequiresVerification(email)
} else if (result.accessToken != null) {
// Registration complete, user is signed in
_uiState.value = AuthUiState.Authenticated
}
} catch (e: InsforgeHttpException) {
_uiState.value = AuthUiState.Error(e.message ?: "Sign up failed")
}
}
// Verify email with 6-digit code
suspend fun verifyEmail(email: String, code: String) {
try {
client.auth.verifyEmail(email = email, code = code)
// Verification successful, user can now sign in
_uiState.value = AuthUiState.VerificationSuccess
} catch (e: InsforgeHttpException) {
_uiState.value = AuthUiState.Error("Invalid verification code")
}
}
// Resend verification email
suspend fun resendVerificationEmail(email: String) {
try {
client.auth.resendVerificationEmail(email = email)
// Show success message
} catch (e: InsforgeHttpException) {
_uiState.value = AuthUiState.Error("Failed to resend verification email")
}
}
}
sealed class AuthUiState {
object Initial : AuthUiState()
object Authenticated : AuthUiState()
object VerificationSuccess : AuthUiState()
data class RequiresVerification(val email: String) : AuthUiState()
data class Error(val message: String) : AuthUiState()
}
電子郵件驗證
對於使用電子郵件註冊的使用者,InsForge 後端提供三種選項:- 無電子郵件驗證 - 使用者可在註冊後立即登入。
SignUpResponse將具有accessToken != null。 - 基於連結的驗證 - 使用者必須開啟電子郵件並按一下驗證連結,然後才能登入。
- 基於代碼的驗證 - InsForge 後端向使用者的電子郵件傳送 6 位驗證代碼。用戶端應用程式需要顯示驗證畫面,使用者可以在其中輸入代碼,然後呼叫
verifyEmail(email, code)以完成驗證。只有之後使用者才能使用電子郵件 + 密碼登入。
requireEmailVerification 為 true 時,回應將具有:
accessToken = nulluser = nullrequireEmailVerification = true
相關方法
| 方法 | 說明 |
|---|---|
verifyEmail(email, code) | 使用 6 位代碼驗證電子郵件 |
resendVerificationEmail(email) | 重新傳送驗證電子郵件 |
signIn()
使用電子郵件和密碼登入現有使用者。範例
try {
val result = client.auth.signIn(
email = "user@example.com",
password = "secure_password123"
)
result.user?.let { user ->
Log.d("Auth", "Welcome back, ${user.profile?.name ?: user.email}")
}
} catch (e: InsforgeHttpException) {
Log.e("Auth", "Sign in failed: ${e.message}")
}
電子郵件驗證
如果登入回應是:{"error":"FORBIDDEN","message":"Email verification required","statusCode":403,"nextActions":"Please verify your email address before logging in"}
signOut()
登出目前使用者。範例
try {
client.auth.signOut()
Log.d("Auth", "User signed out")
} catch (e: InsforgeException) {
Log.e("Auth", "Sign out failed: ${e.message}")
}
signInWithOAuthPage()
直接使用特定 OAuth 提供者登入。此方法在系統瀏覽器中直接開啟 OAuth 提供者的身份驗證頁面。支援的提供者
enum class OAuthProvider(val value: String) {
GOOGLE("google"),
GITHUB("github"),
DISCORD("discord"),
LINKEDIN("linkedin"),
FACEBOOK("facebook"),
INSTAGRAM("instagram"),
TIKTOK("tiktok"),
APPLE("apple"),
X("x"),
SPOTIFY("spotify"),
MICROSOFT("microsoft")
}
參數
provider(OAuthProvider) - 用於身份驗證的 OAuth 提供者redirectUri(String) - 回呼 URL,InsForge 將在身份驗證後重新導向到此 URL
傳回值
String // The OAuth authorization URL (also opens in browser automatically)
範例
- 設定 BrowserLauncher
browserLauncher 以處理開啟 URL:
val client = createInsforgeClient(baseURL, anonKey) {
install(Auth) {
browserLauncher = BrowserLauncher { url ->
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
// Handle non-Activity context safely
if (context is Activity) {
customTabsIntent.launchUrl(context, Uri.parse(url))
} else {
customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
customTabsIntent.launchUrl(context, Uri.parse(url))
}
}
persistSession = true
sessionStorage = mySessionStorage
}
}
- 設定應用程式連結/深層連結回呼
- 選項 A:自訂 URL 配置(用於開發)
<activity
android:name=".AuthCallbackActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" android:host="auth" android:path="/callback" />
</intent-filter>
</activity>
- 選項 B:應用程式連結(用於生產)
<activity
android:name=".AuthCallbackActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="yourdomain.com" android:path="/auth/callback" />
</intent-filter>
</activity>
- 使用特定提供者啟動 OAuth 登入
// Start OAuth flow with Google
fun startGoogleLogin() {
lifecycleScope.launch {
val authUrl = client.auth.signInWithOAuthPage(
OAuthProvider.GOOGLE,
"yourapp://auth/callback"
)
// Browser opens automatically via browserLauncher
}
}
- 處理 OAuth 回呼
class AuthCallbackActivity : AppCompatActivity() {
// Example: read the shared InsForge client from your Application class.
private val client by lazy { (application as MyApp).insforgeClient }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let { handleIntent(it) }
}
private fun handleIntent(intent: Intent) {
intent.data?.let { uri ->
lifecycleScope.launch {
try {
val result = client.auth.handleAuthCallback(uri.toString())
Toast.makeText(
this@AuthCallbackActivity,
"Success: ${result.email}",
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this@AuthCallbackActivity, MainActivity::class.java))
finish()
} catch (e: Exception) {
Toast.makeText(
this@AuthCallbackActivity,
"Failed: ${e.message}",
Toast.LENGTH_LONG
).show()
finish()
}
}
}
}
}
client 應來自您在應用程式中其他地方使用的同一共用 InsForge 用戶端執行個體,例如 Application 單一形式或您的 DI 容器。
getCurrentUser()
從伺服器擷取目前已驗證的使用者。這是一個進行網路請求的暫停函式。傳回值
CurrentUserResponse // Contains user data from server
範例
try {
val response = client.auth.getCurrentUser()
Log.d("Auth", "Email: ${response.email}")
Log.d("Auth", "Name: ${response.profile?.name ?: "N/A"}")
} catch (e: InsforgeHttpException) {
Log.e("Auth", "Failed to get user: ${e.message}")
}
此方法進行網路請求以擷取使用者資料。若要存取本機快取的使用者狀態,請改用
currentUser StateFlow。updateProfile()
更新目前使用者的設定檔。參數
profile(Map\<String, Any\>) - 要更新的設定檔欄位
傳回值
ProfileResponse // Updated profile data
範例
try {
val result = client.auth.updateProfile(
mapOf(
"name" to "JohnDev",
"bio" to "Android Developer",
"avatar_url" to "https://example.com/avatar.jpg"
)
)
Log.d("Auth", "Profile updated: ${result.name}")
} catch (e: InsforgeHttpException) {
Log.e("Auth", "Update failed: ${e.message}")
}
密碼重設
InsForge 支援兩種密碼重設方法,在後端中設定:- 代碼方法:使用者透過電子郵件接收 6 位代碼,驗證該代碼以取得重設權杖,然後重設密碼
- 連結方法:使用者透過電子郵件接收包含重設權杖的魔法連結,然後直接重設密碼
sendPasswordReset()
向使用者傳送密碼重設電子郵件。該電子郵件將包含 6 位代碼或魔法連結,具體取決於後端設定。參數
email(String) - 使用者的電子郵件地址
範例
try {
client.auth.sendPasswordReset(email = "user@example.com")
// Show message to check email
showMessage("If your email is registered, you will receive a password reset email.")
} catch (e: InsforgeHttpException) {
Log.e("Auth", "Failed to send reset email: ${e.message}")
}
exchangeResetPasswordToken()
將 6 位重設代碼交換為重設權杖。此方法僅用於基於代碼的重設流程。參數
email(String) - 使用者的電子郵件地址code(String) - 透過電子郵件接收的 6 位數字代碼
傳回值
ResetTokenResponse
ResetTokenResponse
data class ResetTokenResponse(
/** Reset token to use with resetPassword() */
val token: String,
/** Token expiration timestamp */
val expiresAt: String?
)
範例
try {
val response = client.auth.exchangeResetPasswordToken(
email = "user@example.com",
code = "123456"
)
// Store the token and proceed to password reset screen
val resetToken = response.token
showPasswordResetScreen(token = resetToken)
} catch (e: InsforgeHttpException) {
Log.e("Auth", "Invalid or expired code: ${e.message}")
}
resetPassword()
使用重設權杖重設使用者的密碼。參數
newPassword(String) - 符合設定要求的新密碼otp(String) - 重設權杖(來自代碼流的exchangeResetPasswordToken(),或來自連結流的魔法連結 URL)
範例
try {
client.auth.resetPassword(
newPassword = "newSecurePassword123",
otp = resetToken
)
// Password reset successful
showMessage("Password reset successfully. You can now sign in.")
navigateToSignIn()
} catch (e: InsforgeHttpException) {
Log.e("Auth", "Password reset failed: ${e.message}")
}
錯誤處理
import dev.insforge.exceptions.InsforgeHttpException
import dev.insforge.exceptions.InsforgeException
try {
val result = client.auth.signIn(email, password)
} catch (e: InsforgeHttpException) {
// HTTP errors from API with error codes
when (e.error) {
"INVALID_CREDENTIALS" -> showError("Invalid email or password")
"USER_NOT_FOUND" -> showError("User not found")
"EMAIL_NOT_VERIFIED" -> showError("Please verify your email")
"INVALID_EMAIL" -> showError("Invalid email format")
"WEAK_PASSWORD" -> showError("Password is too weak")
else -> showError("Error: ${e.message}")
}
} catch (e: InsforgeException) {
// Other SDK errors (network, parsing, etc.)
showError("Error: ${e.message}")
}
常見錯誤代碼
| 錯誤代碼 | 說明 |
|---|---|
INVALID_CREDENTIALS | 電子郵件或密碼不正確 |
USER_NOT_FOUND | 不存在此電子郵件的使用者 |
EMAIL_NOT_VERIFIED | 需要電子郵件驗證 |
INVALID_EMAIL | 電子郵件格式無效 |
WEAK_PASSWORD | 密碼不符合要求 |
USER_ALREADY_EXISTS | 電子郵件已註冊 |
SESSION_EXPIRED | 工作階段已過期,需要重新登入 |