diff --git a/src/auth/AuthService.ts b/src/auth/AuthService.ts index 7e76cc5..41127d5 100644 --- a/src/auth/AuthService.ts +++ b/src/auth/AuthService.ts @@ -1,15 +1,15 @@ /** - * AuthService — device-based player authentication. + * AuthService — player authentication. * - * Handles login via device ID (the primary auth flow for game clients), - * logout (token clearing), and auth state observation. + * Handles login via device ID (the primary auth flow for game clients) or a + * custom backend webhook, logout (token clearing), and auth state observation. */ import type { RudderContext } from '../core/context.js'; import type { RemoteConfigShape } from '../state/RemoteConfigState.js'; import { getOrCreateDeviceId } from '../device/DeviceId.js'; import { api } from '../generated/api.js'; -import type { LoginViaDeviceResponse } from '../generated/auth.js'; +import type { LoginViaCustomResponse, LoginViaDeviceResponse } from '../generated/auth.js'; export type AuthState = 'signed-in' | 'signed-out'; @@ -24,6 +24,17 @@ export interface LoginWithDeviceOptions { nickname?: string; } +export interface LoginWithCustomOptions { + /** Arbitrary payload forwarded to the developer's custom auth webhook. */ + customData: Record; + /** Player's region code (default: "global"). */ + region?: string; + /** Player's language code (default: "en"). */ + language?: string; + /** Optional player nickname; omitted from the request when not set. */ + nickname?: string; +} + export class AuthService { private readonly listeners = new Set(); private state: AuthState; @@ -81,6 +92,32 @@ export class AuthService return response; } + /** + * Authenticates via the project's custom authorization webhook and returns + * access + refresh tokens. + * + * The payload is forwarded to the developer's backend; on success, tokens are + * saved to the client's TokenStore. + */ + async loginWithCustom(options: LoginWithCustomOptions): Promise { + const { customData, region = 'global', language = 'en', nickname } = options; + const response = await api.loginViaCustom(this.ctx, { + key: this.ctx.options.projectKey, + customData, + region, + language, + nickname, + }); + + this.ctx.options.tokenStore.saveTokens( + response.accessToken ?? '', + response.refreshToken ?? '', + ); + await this.startRuntime(); + this.setState('signed-in'); + return response; + } + /** Clears all stored tokens (logout). */ logout(): void { this.ctx.options.tokenStore.clear(); diff --git a/src/generated/api.ts b/src/generated/api.ts index 3168755..5b530e2 100644 --- a/src/generated/api.ts +++ b/src/generated/api.ts @@ -1,13 +1,13 @@ // Code generated by apigen. DO NOT EDIT. -import type { LoginViaDeviceRequest, LoginViaDeviceResponse, RefreshAccessTokenRequest, RefreshAccessTokenResponse } from './auth.js'; +import type { LoginViaCustomRequest, LoginViaCustomResponse, LoginViaDeviceRequest, LoginViaDeviceResponse, RefreshAccessTokenRequest, RefreshAccessTokenResponse } from './auth.js'; import type { AddBattlePassXpRequest, AddBattlePassXpResponse, ClaimBattlePassRewardRequest, ClaimBattlePassRewardResponse, GetBattlePassProgressRequest, GetBattlePassProgressResponse, PurchaseBattlePassPremiumRequest, PurchaseBattlePassPremiumResponse } from './battlepass.js'; import type { ListCatalogItemsResponse } from './catalog.js'; import type { GetInventoryResponse } from './inventory.js'; import type { GetRankingResponse, SubmitScoreRequest } from './leaderboards.js'; import type { PlayerProfile } from './player.js'; import type { GetProjectStorageResponse, UpdateProjectStorageRequest } from './project-storage.js'; -import type { ClaimQuestRequest, ClaimQuestResponse, ListQuestsRequest, ListQuestsResponse, ReportQuestProgressRequest, ReportQuestProgressResponse } from './quests.js'; +import type { ClaimQuestRequest, ClaimQuestResponse, ListQuestsResponse, ReportQuestProgressRequest, ReportQuestProgressResponse } from './quests.js'; import type { ListRemoteConfigsResponse, RemoteConfig } from './remote-config.js'; import type { GetScenarioRunRequest, GetScenarioRunResponse, HandleScenarioCallbackRequest, HandleScenarioCallbackResponse, TriggerScenarioRequest, TriggerScenarioResponse, UpdateScenarioCounterRequest, UpdateScenarioCounterResponse } from './scenarios.js'; import type { GetStorageResponse, UpdateStorageRequest } from './storage.js'; @@ -131,9 +131,8 @@ export const api = { listQuests: ( t: Transport, - body: ListQuestsRequest, ): Promise => - t.request('POST', '/sdk/v1/quests/list', body), + t.request('POST', '/sdk/v1/quests/list'), listSdkRemoteConfigs: ( t: Transport, @@ -145,6 +144,12 @@ export const api = { ): Promise => t.request('GET', '/sdk/v1/stores'), + loginViaCustom: ( + t: Transport, + body: LoginViaCustomRequest, + ): Promise => + t.request('POST', '/sdk/v1/authorization/custom', body), + loginViaDevice: ( t: Transport, body: LoginViaDeviceRequest, diff --git a/src/generated/auth.ts b/src/generated/auth.ts index 3885fb1..ab2414a 100644 --- a/src/generated/auth.ts +++ b/src/generated/auth.ts @@ -1,5 +1,18 @@ // Code generated by apigen. DO NOT EDIT. +export interface LoginViaCustomRequest { + "customData"?: { [key: string]: unknown }; + "key"?: string; + "language"?: string; + "nickname"?: string; + "region"?: string; +} + +export interface LoginViaCustomResponse { + "accessToken"?: string; + "refreshToken"?: string; +} + export interface LoginViaDeviceRequest { "deviceId"?: string; "key"?: string; diff --git a/src/generated/battlepass.ts b/src/generated/battlepass.ts index 68c9d74..51917a2 100644 --- a/src/generated/battlepass.ts +++ b/src/generated/battlepass.ts @@ -1,6 +1,6 @@ // Code generated by apigen. DO NOT EDIT. -import type { ExecutionPlan } from './common.js'; +import type { ExecutionPlan, Reward } from './common.js'; export interface AddBattlePassXpRequest { "amount"?: number; @@ -18,24 +18,18 @@ export interface AddBattlePassXpResponse { "xp"?: number; } -export interface BattlePassReward { - "amount"?: number; - "currency"?: string; - "itemId"?: string; -} - export interface ClaimBattlePassRewardRequest { "level"?: number; "nodeId"?: string; "runId"?: string; "scenarioId"?: string; - "track"?: string; + "track"?: "free" | "premium"; } export interface ClaimBattlePassRewardResponse { "alreadyClaimed"?: boolean; "error"?: string; - "granted"?: BattlePassReward[]; + "granted"?: Reward[]; "success"?: boolean; } diff --git a/src/generated/common.ts b/src/generated/common.ts index 586986e..f18d0f0 100644 --- a/src/generated/common.ts +++ b/src/generated/common.ts @@ -13,6 +13,8 @@ export interface BoundaryNode { export interface ErrorResponse { "code"?: "early_completion" | "forbidden" | "level_not_reached" | "node_not_active" | "objectives_incomplete" | "run_expired" | "run_not_active" | "scenario_not_active" | "unknown_run"; "error"?: string; + "index"?: number; + "playerId"?: string; "requestId"?: string; } @@ -42,3 +44,9 @@ export interface PlanEdge { "targetHandle"?: string; } +export interface Reward { + "amount"?: number; + "currency"?: string; + "itemId"?: string; +} + diff --git a/src/generated/quests.ts b/src/generated/quests.ts index 7f50342..43b1782 100644 --- a/src/generated/quests.ts +++ b/src/generated/quests.ts @@ -1,5 +1,7 @@ // Code generated by apigen. DO NOT EDIT. +import type { Reward } from './common.js'; + export interface ClaimQuestRequest { "questId"?: string; } @@ -7,13 +9,10 @@ export interface ClaimQuestRequest { export interface ClaimQuestResponse { "alreadyClaimed"?: boolean; "error"?: string; - "granted"?: QuestReward[]; + "granted"?: Reward[]; "success"?: boolean; } -export interface ListQuestsRequest { -} - export interface ListQuestsResponse { "quests"?: Quest[]; } @@ -22,8 +21,8 @@ export interface Quest { "id"?: string; "name"?: string; "objectives"?: QuestObjectiveProgress[]; - "rewards"?: QuestReward[]; - "status"?: string; + "rewards"?: Reward[]; + "status"?: "active" | "claimed" | "completed"; } export interface QuestObjectiveProgress { @@ -34,12 +33,6 @@ export interface QuestObjectiveProgress { "target"?: number; } -export interface QuestReward { - "amount"?: number; - "currency"?: string; - "itemId"?: string; -} - export interface ReportQuestProgressRequest { "amount"?: number; "metric"?: string; diff --git a/src/generated/remote-config.ts b/src/generated/remote-config.ts index 5023e90..7e2466d 100644 --- a/src/generated/remote-config.ts +++ b/src/generated/remote-config.ts @@ -14,6 +14,6 @@ export interface RemoteConfig { "projectId"?: string; "updatedAt"?: string; "value"?: string; - "valueType"?: string; + "valueType"?: "bool" | "float" | "int" | "json" | "string"; } diff --git a/src/generated/scenarios.ts b/src/generated/scenarios.ts index bc9d143..e886378 100644 --- a/src/generated/scenarios.ts +++ b/src/generated/scenarios.ts @@ -9,7 +9,7 @@ export interface GetScenarioRunRequest { export interface GetScenarioRunResponse { "plan"?: ExecutionPlan; "runId"?: string; - "status"?: string; + "status"?: "active" | "completed" | "expired"; } export interface HandleScenarioCallbackRequest { diff --git a/src/index.ts b/src/index.ts index fc791f4..917f12e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ export type { AuthService, AuthState, AuthStateListener, + LoginWithCustomOptions, LoginWithDeviceOptions, } from './auth/AuthService.js'; export type { LeaderboardsService, LeaderboardHandle } from './leaderboards/LeaderboardsService.js'; @@ -88,14 +89,13 @@ export type { ProjectStorageUpdateItem, } from './generated/project-storage.js'; export type { RemoteConfig } from './generated/remote-config.js'; +export type { Reward } from './generated/common.js'; export type { Quest, QuestObjectiveProgress, - QuestReward, ClaimQuestResponse, } from './generated/quests.js'; export type { - BattlePassReward, ClaimedTier, AddBattlePassXpRequest, AddBattlePassXpResponse,