playkit-sdk 1.1.0-beta → 1.1.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * playkit-sdk v1.1.0-beta
2
+ * playkit-sdk v1.1.1-beta
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -210,7 +210,7 @@ class TokenStorage {
210
210
  const translations$1 = {
211
211
  en: {
212
212
  signIn: 'Sign In / Register',
213
- signInSubtitle: 'If you don\'t have an account, we\'ll create one for you. Sign up and get free credits!',
213
+ signInSubtitle: 'This game uses PlayKit for cost management.\nIf you don\'t have an account, we\'ll automatically register you. Sign up and get free AI game credits!',
214
214
  email: 'Email',
215
215
  phone: 'Phone',
216
216
  emailPlaceholder: 'Enter your email address',
@@ -228,7 +228,7 @@ const translations$1 = {
228
228
  },
229
229
  zh: {
230
230
  signIn: '登录/注册',
231
- signInSubtitle: '如果您没有帐户,我们会为您自动注册。注册即送积分!',
231
+ signInSubtitle: '本游戏使用PlayKit进行成本管理。\n如果您没有帐户,我们会为您自动注册。注册即送AI游戏积分!',
232
232
  email: '邮箱',
233
233
  phone: '手机',
234
234
  emailPlaceholder: '请输入邮箱地址',
@@ -246,7 +246,7 @@ const translations$1 = {
246
246
  },
247
247
  'zh-TW': {
248
248
  signIn: '登入/註冊',
249
- signInSubtitle: '如果您沒有帳戶,我們會為您自動註冊。註冊即送積分!',
249
+ signInSubtitle: '本遊戲使用PlayKit進行成本管理。\n如果您沒有帳戶,我們會為您自動註冊。註冊即送AI遊戲積分!',
250
250
  email: '電子郵件',
251
251
  phone: '手機',
252
252
  emailPlaceholder: '請輸入電子郵件地址',
@@ -264,7 +264,7 @@ const translations$1 = {
264
264
  },
265
265
  ja: {
266
266
  signIn: 'サインイン/登録',
267
- signInSubtitle: 'アカウントをお持ちでない場合は、自動的に作成します。登録すると無料クレジットがもらえます!',
267
+ signInSubtitle: 'このゲームはPlayKitでコスト管理を行っています。\nアカウントをお持ちでない場合は、自動的に登録します。登録するとAIゲームクレジットがもらえます!',
268
268
  email: 'メール',
269
269
  phone: '電話',
270
270
  emailPlaceholder: 'メールアドレスを入力してください',
@@ -282,7 +282,7 @@ const translations$1 = {
282
282
  },
283
283
  ko: {
284
284
  signIn: '로그인/가입',
285
- signInSubtitle: '계정이 없으시면 자동으로 생성해 드립니다. 가입하면 무료 크레딧을 받으세요!',
285
+ signInSubtitle: '이 게임은 PlayKit으로 비용 관리를 합니다.\n계정이 없으시면 자동으로 등록해 드립니다. 가입하면 AI 게임 크레딧을 받으세요!',
286
286
  email: '이메일',
287
287
  phone: '전화',
288
288
  emailPlaceholder: '이메일 주소를 입력하세요',
@@ -300,7 +300,7 @@ const translations$1 = {
300
300
  },
301
301
  };
302
302
  class AuthFlowManager extends EventEmitter {
303
- constructor(baseURL = 'https://playkit.agentlandlab.com') {
303
+ constructor(baseURL) {
304
304
  super();
305
305
  this.currentSessionId = null;
306
306
  this.uiContainer = null;
@@ -311,7 +311,8 @@ class AuthFlowManager extends EventEmitter {
311
311
  this.identifierPanel = null;
312
312
  this.verificationPanel = null;
313
313
  this.loadingOverlay = null;
314
- this.baseURL = baseURL;
314
+ // @ts-ignore - replaced at build time
315
+ this.baseURL = baseURL || "https://playkit.agentlandlab.com";
315
316
  this.currentLanguage = this.detectLanguage();
316
317
  }
317
318
  /**
@@ -801,6 +802,10 @@ class AuthFlowManager extends EventEmitter {
801
802
  codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, index) => {
802
803
  input.addEventListener('input', (e) => {
803
804
  const target = e.target;
805
+ // Only allow digits and limit to 1 character
806
+ const value = target.value.replace(/\D/g, '');
807
+ target.value = value.slice(0, 1);
808
+ // Move to next input if a digit was entered
804
809
  if (target.value.length === 1 && index < codeInputs.length - 1) {
805
810
  codeInputs[index + 1].focus();
806
811
  }
@@ -1052,16 +1057,178 @@ class AuthFlowManager extends EventEmitter {
1052
1057
  }
1053
1058
  }
1054
1059
 
1060
+ /**
1061
+ * External Authentication Flow Manager
1062
+ * Manages OAuth-like popup authentication flow for external_auth channel
1063
+ */
1064
+ class ExternalAuthFlowManager extends EventEmitter {
1065
+ constructor(baseURL, gameId) {
1066
+ super();
1067
+ this.baseURL = baseURL;
1068
+ this.gameId = gameId;
1069
+ }
1070
+ /**
1071
+ * Generate a random string for PKCE code verifier
1072
+ * @private
1073
+ */
1074
+ generateCodeVerifier() {
1075
+ const array = new Uint8Array(32);
1076
+ crypto.getRandomValues(array);
1077
+ return this.base64URLEncode(array);
1078
+ }
1079
+ /**
1080
+ * Generate PKCE code challenge from verifier
1081
+ * @private
1082
+ */
1083
+ async generateCodeChallenge(verifier) {
1084
+ const encoder = new TextEncoder();
1085
+ const data = encoder.encode(verifier);
1086
+ const hash = await crypto.subtle.digest('SHA-256', data);
1087
+ return this.base64URLEncode(new Uint8Array(hash));
1088
+ }
1089
+ /**
1090
+ * Base64 URL encode
1091
+ * @private
1092
+ */
1093
+ base64URLEncode(buffer) {
1094
+ const base64 = btoa(String.fromCharCode(...buffer));
1095
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
1096
+ }
1097
+ /**
1098
+ * Generate a random state for CSRF protection
1099
+ * @private
1100
+ */
1101
+ generateState() {
1102
+ const array = new Uint8Array(16);
1103
+ crypto.getRandomValues(array);
1104
+ return this.base64URLEncode(array);
1105
+ }
1106
+ /**
1107
+ * Start the external authentication flow
1108
+ * Opens a popup window for user to login and authorize
1109
+ *
1110
+ * @returns {Promise<string>} Player token
1111
+ */
1112
+ async startFlow() {
1113
+ return new Promise(async (resolve, reject) => {
1114
+ // Generate PKCE parameters
1115
+ const codeVerifier = this.generateCodeVerifier();
1116
+ const codeChallenge = await this.generateCodeChallenge(codeVerifier);
1117
+ const state = this.generateState();
1118
+ // Build authorization URL with postmessage redirect
1119
+ const redirectUri = 'postmessage';
1120
+ const params = new URLSearchParams({
1121
+ response_type: 'code',
1122
+ game_id: this.gameId,
1123
+ redirect_uri: redirectUri,
1124
+ code_challenge: codeChallenge,
1125
+ code_challenge_method: 'S256',
1126
+ state: state,
1127
+ });
1128
+ const authUrl = `${this.baseURL}/external-auth/authorize?${params}`;
1129
+ // Open popup window
1130
+ const popup = window.open(authUrl, 'agentland_external_auth', 'width=500,height=700,scrollbars=yes');
1131
+ if (!popup) {
1132
+ reject(new PlayKitError('Failed to open popup window. Please allow popups for this site.', 'POPUP_BLOCKED'));
1133
+ return;
1134
+ }
1135
+ // Listen for messages from popup
1136
+ const messageHandler = async (event) => {
1137
+ // Verify origin
1138
+ const allowedOrigin = new URL(this.baseURL).origin;
1139
+ if (event.origin !== allowedOrigin) {
1140
+ return;
1141
+ }
1142
+ // Check message type
1143
+ if (event.data.type !== 'external_auth_callback') {
1144
+ return;
1145
+ }
1146
+ // Cleanup
1147
+ window.removeEventListener('message', messageHandler);
1148
+ clearInterval(checkClosed);
1149
+ if (popup && !popup.closed) {
1150
+ popup.close();
1151
+ }
1152
+ try {
1153
+ // Extract data from message
1154
+ const { code, state: returnedState, error, error_description } = event.data;
1155
+ // Check for errors
1156
+ if (error) {
1157
+ reject(new PlayKitError(error_description || error, 'AUTH_ERROR'));
1158
+ return;
1159
+ }
1160
+ // Verify state
1161
+ if (returnedState !== state) {
1162
+ reject(new PlayKitError('State mismatch - possible CSRF attack', 'STATE_MISMATCH'));
1163
+ return;
1164
+ }
1165
+ if (!code) {
1166
+ reject(new PlayKitError('No authorization code received', 'NO_CODE'));
1167
+ return;
1168
+ }
1169
+ // Exchange code for token
1170
+ const token = await this.exchangeCodeForToken(code, codeVerifier, redirectUri);
1171
+ resolve(token);
1172
+ }
1173
+ catch (err) {
1174
+ reject(err);
1175
+ }
1176
+ };
1177
+ window.addEventListener('message', messageHandler);
1178
+ // Check if popup was closed without completing auth
1179
+ const checkClosed = setInterval(() => {
1180
+ if (popup.closed) {
1181
+ clearInterval(checkClosed);
1182
+ window.removeEventListener('message', messageHandler);
1183
+ reject(new PlayKitError('Authentication cancelled by user', 'USER_CANCELLED'));
1184
+ }
1185
+ }, 1000);
1186
+ });
1187
+ }
1188
+ /**
1189
+ * Exchange authorization code for access token
1190
+ * @private
1191
+ */
1192
+ async exchangeCodeForToken(code, codeVerifier, redirectUri) {
1193
+ const response = await fetch(`${this.baseURL}/api/external-auth/token`, {
1194
+ method: 'POST',
1195
+ headers: {
1196
+ 'Content-Type': 'application/json',
1197
+ },
1198
+ body: JSON.stringify({
1199
+ grant_type: 'authorization_code',
1200
+ code: code,
1201
+ code_verifier: codeVerifier,
1202
+ redirect_uri: redirectUri,
1203
+ }),
1204
+ });
1205
+ if (!response.ok) {
1206
+ const error = await response.json().catch(() => ({ error_description: 'Token exchange failed' }));
1207
+ throw new PlayKitError(error.error_description || 'Token exchange failed', 'TOKEN_EXCHANGE_FAILED', response.status);
1208
+ }
1209
+ const data = await response.json();
1210
+ return data.access_token;
1211
+ }
1212
+ /**
1213
+ * Clean up
1214
+ */
1215
+ destroy() {
1216
+ this.removeAllListeners();
1217
+ }
1218
+ }
1219
+
1055
1220
  /**
1056
1221
  * Authentication manager
1057
1222
  * Handles JWT exchange and token management
1058
1223
  */
1059
- const DEFAULT_BASE_URL$3 = 'https://playkit.agentlandlab.com';
1224
+ // @ts-ignore - replaced at build time
1225
+ const DEFAULT_BASE_URL$3 = "https://playkit.agentlandlab.com";
1060
1226
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
1061
1227
  class AuthManager extends EventEmitter {
1062
1228
  constructor(config) {
1063
1229
  super();
1064
1230
  this.authFlowManager = null;
1231
+ this.externalAuthFlowManager = null;
1065
1232
  this.config = config;
1066
1233
  this.storage = new TokenStorage();
1067
1234
  this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
@@ -1115,7 +1282,9 @@ class AuthManager extends EventEmitter {
1115
1282
  this.emit('unauthenticated');
1116
1283
  // Auto-start login flow in browser environment
1117
1284
  if (typeof window !== 'undefined') {
1118
- await this.startAuthFlow();
1285
+ // Default to external-auth if not specified
1286
+ const useExternalAuth = this.config.authMethod !== 'headless';
1287
+ await this.startAuthFlow(useExternalAuth);
1119
1288
  // If we reach here, authentication was successful
1120
1289
  // If it failed, startAuthFlow() will have thrown an error
1121
1290
  }
@@ -1126,27 +1295,53 @@ class AuthManager extends EventEmitter {
1126
1295
  }
1127
1296
  /**
1128
1297
  * Start the authentication flow UI
1298
+ *
1299
+ * @param useExternalAuth - Use external-auth OAuth flow instead of headless flow
1129
1300
  */
1130
- async startAuthFlow() {
1131
- var _a;
1132
- if (this.authFlowManager) {
1301
+ async startAuthFlow(useExternalAuth = false) {
1302
+ var _a, _b;
1303
+ if (this.authFlowManager || this.externalAuthFlowManager) {
1133
1304
  // Already in progress
1134
1305
  return;
1135
1306
  }
1136
1307
  try {
1137
- this.authFlowManager = new AuthFlowManager(this.baseURL);
1138
- // Get global token from auth flow
1139
- const globalToken = await this.authFlowManager.startFlow();
1140
- // Exchange for player token
1141
- await this.exchangeJWT(globalToken);
1142
- // Clean up
1143
- this.authFlowManager.destroy();
1144
- this.authFlowManager = null;
1308
+ if (useExternalAuth) {
1309
+ // Use external-auth OAuth popup flow
1310
+ this.externalAuthFlowManager = new ExternalAuthFlowManager(this.baseURL, this.config.gameId);
1311
+ // Get player token directly from external-auth flow
1312
+ const playerToken = await this.externalAuthFlowManager.startFlow();
1313
+ // Update auth state with the player token
1314
+ this.authState = {
1315
+ isAuthenticated: true,
1316
+ token: playerToken,
1317
+ tokenType: 'player',
1318
+ };
1319
+ // Save to storage
1320
+ await this.storage.saveAuthState(this.config.gameId, this.authState);
1321
+ await this.storage.saveSharedToken(playerToken);
1322
+ this.emit('authenticated', this.authState);
1323
+ // Clean up
1324
+ this.externalAuthFlowManager.destroy();
1325
+ this.externalAuthFlowManager = null;
1326
+ }
1327
+ else {
1328
+ // Use headless verification code flow
1329
+ this.authFlowManager = new AuthFlowManager(this.baseURL);
1330
+ // Get global token from auth flow
1331
+ const globalToken = await this.authFlowManager.startFlow();
1332
+ // Exchange for player token
1333
+ await this.exchangeJWT(globalToken);
1334
+ // Clean up
1335
+ this.authFlowManager.destroy();
1336
+ this.authFlowManager = null;
1337
+ }
1145
1338
  }
1146
1339
  catch (error) {
1147
1340
  // User canceled or error occurred
1148
1341
  (_a = this.authFlowManager) === null || _a === void 0 ? void 0 : _a.destroy();
1149
1342
  this.authFlowManager = null;
1343
+ (_b = this.externalAuthFlowManager) === null || _b === void 0 ? void 0 : _b.destroy();
1344
+ this.externalAuthFlowManager = null;
1150
1345
  // Re-emit error
1151
1346
  this.emit('error', error);
1152
1347
  throw error;
@@ -2787,8 +2982,10 @@ class PlayKitSDK extends EventEmitter {
2787
2982
  }
2788
2983
  }
2789
2984
 
2985
+ exports.AuthFlowManager = AuthFlowManager;
2790
2986
  exports.AuthManager = AuthManager;
2791
2987
  exports.ChatClient = ChatClient;
2988
+ exports.ExternalAuthFlowManager = ExternalAuthFlowManager;
2792
2989
  exports.ImageClient = ImageClient;
2793
2990
  exports.NPCClient = NPCClient;
2794
2991
  exports.PlayKitSDK = PlayKitSDK;