playkit-sdk 1.1.0-beta-pr → 1.1.2-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-pr
2
+ * playkit-sdk v1.1.2-beta
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -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
  /**
@@ -1056,16 +1057,250 @@ class AuthFlowManager extends EventEmitter {
1056
1057
  }
1057
1058
  }
1058
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
+ * Shows an iframe modal 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
+ // Create iframe modal
1130
+ this.createAuthModal(authUrl);
1131
+ // Listen for messages from iframe
1132
+ const messageHandler = async (event) => {
1133
+ // Verify origin
1134
+ const allowedOrigin = new URL(this.baseURL).origin;
1135
+ if (event.origin !== allowedOrigin) {
1136
+ return;
1137
+ }
1138
+ // Check message type
1139
+ if (event.data.type !== 'external_auth_callback') {
1140
+ return;
1141
+ }
1142
+ // Cleanup
1143
+ window.removeEventListener('message', messageHandler);
1144
+ this.destroyAuthModal();
1145
+ try {
1146
+ // Extract data from message
1147
+ const { code, state: returnedState, error, error_description } = event.data;
1148
+ // Check for errors
1149
+ if (error) {
1150
+ reject(new PlayKitError(error_description || error, 'AUTH_ERROR'));
1151
+ return;
1152
+ }
1153
+ // Verify state
1154
+ if (returnedState !== state) {
1155
+ reject(new PlayKitError('State mismatch - possible CSRF attack', 'STATE_MISMATCH'));
1156
+ return;
1157
+ }
1158
+ if (!code) {
1159
+ reject(new PlayKitError('No authorization code received', 'NO_CODE'));
1160
+ return;
1161
+ }
1162
+ // Exchange code for token
1163
+ const token = await this.exchangeCodeForToken(code, codeVerifier, redirectUri);
1164
+ resolve(token);
1165
+ }
1166
+ catch (err) {
1167
+ reject(err);
1168
+ }
1169
+ };
1170
+ window.addEventListener('message', messageHandler);
1171
+ });
1172
+ }
1173
+ /**
1174
+ * Create authentication modal with iframe
1175
+ * @private
1176
+ */
1177
+ createAuthModal(authUrl) {
1178
+ // Create modal container
1179
+ const modal = document.createElement('div');
1180
+ modal.id = 'playkit-external-auth-modal';
1181
+ modal.style.cssText = `
1182
+ position: fixed;
1183
+ top: 0;
1184
+ left: 0;
1185
+ right: 0;
1186
+ bottom: 0;
1187
+ z-index: 999999;
1188
+ display: flex;
1189
+ justify-content: center;
1190
+ align-items: center;
1191
+ background: rgba(0, 0, 0, 0.5);
1192
+ backdrop-filter: blur(4px);
1193
+ `;
1194
+ // Create close button
1195
+ const closeButton = document.createElement('button');
1196
+ closeButton.innerHTML = '×';
1197
+ closeButton.style.cssText = `
1198
+ position: absolute;
1199
+ top: 20px;
1200
+ right: 20px;
1201
+ width: 40px;
1202
+ height: 40px;
1203
+ border: none;
1204
+ background: rgba(255, 255, 255, 0.9);
1205
+ color: #333;
1206
+ font-size: 32px;
1207
+ line-height: 32px;
1208
+ cursor: pointer;
1209
+ border-radius: 50%;
1210
+ z-index: 1000000;
1211
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
1212
+ transition: background 0.2s;
1213
+ `;
1214
+ closeButton.onmouseover = () => {
1215
+ closeButton.style.background = 'rgba(255, 255, 255, 1)';
1216
+ };
1217
+ closeButton.onmouseout = () => {
1218
+ closeButton.style.background = 'rgba(255, 255, 255, 0.9)';
1219
+ };
1220
+ closeButton.onclick = () => {
1221
+ this.destroyAuthModal();
1222
+ };
1223
+ // Create iframe container
1224
+ const iframeContainer = document.createElement('div');
1225
+ iframeContainer.style.cssText = `
1226
+ position: relative;
1227
+ width: 90%;
1228
+ max-width: 480px;
1229
+ height: 90%;
1230
+ max-height: 700px;
1231
+ background: white;
1232
+ border-radius: 8px;
1233
+ overflow: hidden;
1234
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
1235
+ `;
1236
+ // Create iframe
1237
+ const iframe = document.createElement('iframe');
1238
+ iframe.src = authUrl;
1239
+ iframe.style.cssText = `
1240
+ width: 100%;
1241
+ height: 100%;
1242
+ border: none;
1243
+ `;
1244
+ iframe.allow = 'clipboard-write';
1245
+ iframeContainer.appendChild(iframe);
1246
+ modal.appendChild(closeButton);
1247
+ modal.appendChild(iframeContainer);
1248
+ document.body.appendChild(modal);
1249
+ }
1250
+ /**
1251
+ * Destroy authentication modal
1252
+ * @private
1253
+ */
1254
+ destroyAuthModal() {
1255
+ const modal = document.getElementById('playkit-external-auth-modal');
1256
+ if (modal) {
1257
+ modal.remove();
1258
+ }
1259
+ }
1260
+ /**
1261
+ * Exchange authorization code for access token
1262
+ * @private
1263
+ */
1264
+ async exchangeCodeForToken(code, codeVerifier, redirectUri) {
1265
+ const response = await fetch(`${this.baseURL}/api/external-auth/token`, {
1266
+ method: 'POST',
1267
+ headers: {
1268
+ 'Content-Type': 'application/json',
1269
+ },
1270
+ body: JSON.stringify({
1271
+ grant_type: 'authorization_code',
1272
+ code: code,
1273
+ code_verifier: codeVerifier,
1274
+ redirect_uri: redirectUri,
1275
+ }),
1276
+ });
1277
+ if (!response.ok) {
1278
+ const error = await response.json().catch(() => ({ error_description: 'Token exchange failed' }));
1279
+ throw new PlayKitError(error.error_description || 'Token exchange failed', 'TOKEN_EXCHANGE_FAILED', response.status);
1280
+ }
1281
+ const data = await response.json();
1282
+ return data.access_token;
1283
+ }
1284
+ /**
1285
+ * Clean up
1286
+ */
1287
+ destroy() {
1288
+ this.removeAllListeners();
1289
+ }
1290
+ }
1291
+
1059
1292
  /**
1060
1293
  * Authentication manager
1061
1294
  * Handles JWT exchange and token management
1062
1295
  */
1063
- const DEFAULT_BASE_URL$3 = 'https://playkit.agentlandlab.com';
1296
+ // @ts-ignore - replaced at build time
1297
+ const DEFAULT_BASE_URL$3 = "https://playkit.agentlandlab.com";
1064
1298
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
1065
1299
  class AuthManager extends EventEmitter {
1066
1300
  constructor(config) {
1067
1301
  super();
1068
1302
  this.authFlowManager = null;
1303
+ this.externalAuthFlowManager = null;
1069
1304
  this.config = config;
1070
1305
  this.storage = new TokenStorage();
1071
1306
  this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
@@ -1119,7 +1354,9 @@ class AuthManager extends EventEmitter {
1119
1354
  this.emit('unauthenticated');
1120
1355
  // Auto-start login flow in browser environment
1121
1356
  if (typeof window !== 'undefined') {
1122
- await this.startAuthFlow();
1357
+ // Default to external-auth if not specified
1358
+ const useExternalAuth = this.config.authMethod !== 'headless';
1359
+ await this.startAuthFlow(useExternalAuth);
1123
1360
  // If we reach here, authentication was successful
1124
1361
  // If it failed, startAuthFlow() will have thrown an error
1125
1362
  }
@@ -1130,27 +1367,53 @@ class AuthManager extends EventEmitter {
1130
1367
  }
1131
1368
  /**
1132
1369
  * Start the authentication flow UI
1370
+ *
1371
+ * @param useExternalAuth - Use external-auth OAuth flow instead of headless flow
1133
1372
  */
1134
- async startAuthFlow() {
1135
- var _a;
1136
- if (this.authFlowManager) {
1373
+ async startAuthFlow(useExternalAuth = false) {
1374
+ var _a, _b;
1375
+ if (this.authFlowManager || this.externalAuthFlowManager) {
1137
1376
  // Already in progress
1138
1377
  return;
1139
1378
  }
1140
1379
  try {
1141
- this.authFlowManager = new AuthFlowManager(this.baseURL);
1142
- // Get global token from auth flow
1143
- const globalToken = await this.authFlowManager.startFlow();
1144
- // Exchange for player token
1145
- await this.exchangeJWT(globalToken);
1146
- // Clean up
1147
- this.authFlowManager.destroy();
1148
- this.authFlowManager = null;
1380
+ if (useExternalAuth) {
1381
+ // Use external-auth OAuth popup flow
1382
+ this.externalAuthFlowManager = new ExternalAuthFlowManager(this.baseURL, this.config.gameId);
1383
+ // Get player token directly from external-auth flow
1384
+ const playerToken = await this.externalAuthFlowManager.startFlow();
1385
+ // Update auth state with the player token
1386
+ this.authState = {
1387
+ isAuthenticated: true,
1388
+ token: playerToken,
1389
+ tokenType: 'player',
1390
+ };
1391
+ // Save to storage
1392
+ await this.storage.saveAuthState(this.config.gameId, this.authState);
1393
+ await this.storage.saveSharedToken(playerToken);
1394
+ this.emit('authenticated', this.authState);
1395
+ // Clean up
1396
+ this.externalAuthFlowManager.destroy();
1397
+ this.externalAuthFlowManager = null;
1398
+ }
1399
+ else {
1400
+ // Use headless verification code flow
1401
+ this.authFlowManager = new AuthFlowManager(this.baseURL);
1402
+ // Get global token from auth flow
1403
+ const globalToken = await this.authFlowManager.startFlow();
1404
+ // Exchange for player token
1405
+ await this.exchangeJWT(globalToken);
1406
+ // Clean up
1407
+ this.authFlowManager.destroy();
1408
+ this.authFlowManager = null;
1409
+ }
1149
1410
  }
1150
1411
  catch (error) {
1151
1412
  // User canceled or error occurred
1152
1413
  (_a = this.authFlowManager) === null || _a === void 0 ? void 0 : _a.destroy();
1153
1414
  this.authFlowManager = null;
1415
+ (_b = this.externalAuthFlowManager) === null || _b === void 0 ? void 0 : _b.destroy();
1416
+ this.externalAuthFlowManager = null;
1154
1417
  // Re-emit error
1155
1418
  this.emit('error', error);
1156
1419
  throw error;
@@ -2791,8 +3054,10 @@ class PlayKitSDK extends EventEmitter {
2791
3054
  }
2792
3055
  }
2793
3056
 
3057
+ exports.AuthFlowManager = AuthFlowManager;
2794
3058
  exports.AuthManager = AuthManager;
2795
3059
  exports.ChatClient = ChatClient;
3060
+ exports.ExternalAuthFlowManager = ExternalAuthFlowManager;
2796
3061
  exports.ImageClient = ImageClient;
2797
3062
  exports.NPCClient = NPCClient;
2798
3063
  exports.PlayKitSDK = PlayKitSDK;