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
  */
@@ -296,7 +296,7 @@ const translations$1 = {
296
296
  },
297
297
  };
298
298
  class AuthFlowManager extends EventEmitter {
299
- constructor(baseURL = 'https://playkit.agentlandlab.com') {
299
+ constructor(baseURL) {
300
300
  super();
301
301
  this.currentSessionId = null;
302
302
  this.uiContainer = null;
@@ -307,7 +307,8 @@ class AuthFlowManager extends EventEmitter {
307
307
  this.identifierPanel = null;
308
308
  this.verificationPanel = null;
309
309
  this.loadingOverlay = null;
310
- this.baseURL = baseURL;
310
+ // @ts-ignore - replaced at build time
311
+ this.baseURL = baseURL || "https://playkit.agentlandlab.com";
311
312
  this.currentLanguage = this.detectLanguage();
312
313
  }
313
314
  /**
@@ -1052,16 +1053,250 @@ class AuthFlowManager extends EventEmitter {
1052
1053
  }
1053
1054
  }
1054
1055
 
1056
+ /**
1057
+ * External Authentication Flow Manager
1058
+ * Manages OAuth-like popup authentication flow for external_auth channel
1059
+ */
1060
+ class ExternalAuthFlowManager extends EventEmitter {
1061
+ constructor(baseURL, gameId) {
1062
+ super();
1063
+ this.baseURL = baseURL;
1064
+ this.gameId = gameId;
1065
+ }
1066
+ /**
1067
+ * Generate a random string for PKCE code verifier
1068
+ * @private
1069
+ */
1070
+ generateCodeVerifier() {
1071
+ const array = new Uint8Array(32);
1072
+ crypto.getRandomValues(array);
1073
+ return this.base64URLEncode(array);
1074
+ }
1075
+ /**
1076
+ * Generate PKCE code challenge from verifier
1077
+ * @private
1078
+ */
1079
+ async generateCodeChallenge(verifier) {
1080
+ const encoder = new TextEncoder();
1081
+ const data = encoder.encode(verifier);
1082
+ const hash = await crypto.subtle.digest('SHA-256', data);
1083
+ return this.base64URLEncode(new Uint8Array(hash));
1084
+ }
1085
+ /**
1086
+ * Base64 URL encode
1087
+ * @private
1088
+ */
1089
+ base64URLEncode(buffer) {
1090
+ const base64 = btoa(String.fromCharCode(...buffer));
1091
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
1092
+ }
1093
+ /**
1094
+ * Generate a random state for CSRF protection
1095
+ * @private
1096
+ */
1097
+ generateState() {
1098
+ const array = new Uint8Array(16);
1099
+ crypto.getRandomValues(array);
1100
+ return this.base64URLEncode(array);
1101
+ }
1102
+ /**
1103
+ * Start the external authentication flow
1104
+ * Shows an iframe modal for user to login and authorize
1105
+ *
1106
+ * @returns {Promise<string>} Player token
1107
+ */
1108
+ async startFlow() {
1109
+ return new Promise(async (resolve, reject) => {
1110
+ // Generate PKCE parameters
1111
+ const codeVerifier = this.generateCodeVerifier();
1112
+ const codeChallenge = await this.generateCodeChallenge(codeVerifier);
1113
+ const state = this.generateState();
1114
+ // Build authorization URL with postmessage redirect
1115
+ const redirectUri = 'postmessage';
1116
+ const params = new URLSearchParams({
1117
+ response_type: 'code',
1118
+ game_id: this.gameId,
1119
+ redirect_uri: redirectUri,
1120
+ code_challenge: codeChallenge,
1121
+ code_challenge_method: 'S256',
1122
+ state: state,
1123
+ });
1124
+ const authUrl = `${this.baseURL}/external-auth/authorize?${params}`;
1125
+ // Create iframe modal
1126
+ this.createAuthModal(authUrl);
1127
+ // Listen for messages from iframe
1128
+ const messageHandler = async (event) => {
1129
+ // Verify origin
1130
+ const allowedOrigin = new URL(this.baseURL).origin;
1131
+ if (event.origin !== allowedOrigin) {
1132
+ return;
1133
+ }
1134
+ // Check message type
1135
+ if (event.data.type !== 'external_auth_callback') {
1136
+ return;
1137
+ }
1138
+ // Cleanup
1139
+ window.removeEventListener('message', messageHandler);
1140
+ this.destroyAuthModal();
1141
+ try {
1142
+ // Extract data from message
1143
+ const { code, state: returnedState, error, error_description } = event.data;
1144
+ // Check for errors
1145
+ if (error) {
1146
+ reject(new PlayKitError(error_description || error, 'AUTH_ERROR'));
1147
+ return;
1148
+ }
1149
+ // Verify state
1150
+ if (returnedState !== state) {
1151
+ reject(new PlayKitError('State mismatch - possible CSRF attack', 'STATE_MISMATCH'));
1152
+ return;
1153
+ }
1154
+ if (!code) {
1155
+ reject(new PlayKitError('No authorization code received', 'NO_CODE'));
1156
+ return;
1157
+ }
1158
+ // Exchange code for token
1159
+ const token = await this.exchangeCodeForToken(code, codeVerifier, redirectUri);
1160
+ resolve(token);
1161
+ }
1162
+ catch (err) {
1163
+ reject(err);
1164
+ }
1165
+ };
1166
+ window.addEventListener('message', messageHandler);
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Create authentication modal with iframe
1171
+ * @private
1172
+ */
1173
+ createAuthModal(authUrl) {
1174
+ // Create modal container
1175
+ const modal = document.createElement('div');
1176
+ modal.id = 'playkit-external-auth-modal';
1177
+ modal.style.cssText = `
1178
+ position: fixed;
1179
+ top: 0;
1180
+ left: 0;
1181
+ right: 0;
1182
+ bottom: 0;
1183
+ z-index: 999999;
1184
+ display: flex;
1185
+ justify-content: center;
1186
+ align-items: center;
1187
+ background: rgba(0, 0, 0, 0.5);
1188
+ backdrop-filter: blur(4px);
1189
+ `;
1190
+ // Create close button
1191
+ const closeButton = document.createElement('button');
1192
+ closeButton.innerHTML = '×';
1193
+ closeButton.style.cssText = `
1194
+ position: absolute;
1195
+ top: 20px;
1196
+ right: 20px;
1197
+ width: 40px;
1198
+ height: 40px;
1199
+ border: none;
1200
+ background: rgba(255, 255, 255, 0.9);
1201
+ color: #333;
1202
+ font-size: 32px;
1203
+ line-height: 32px;
1204
+ cursor: pointer;
1205
+ border-radius: 50%;
1206
+ z-index: 1000000;
1207
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
1208
+ transition: background 0.2s;
1209
+ `;
1210
+ closeButton.onmouseover = () => {
1211
+ closeButton.style.background = 'rgba(255, 255, 255, 1)';
1212
+ };
1213
+ closeButton.onmouseout = () => {
1214
+ closeButton.style.background = 'rgba(255, 255, 255, 0.9)';
1215
+ };
1216
+ closeButton.onclick = () => {
1217
+ this.destroyAuthModal();
1218
+ };
1219
+ // Create iframe container
1220
+ const iframeContainer = document.createElement('div');
1221
+ iframeContainer.style.cssText = `
1222
+ position: relative;
1223
+ width: 90%;
1224
+ max-width: 480px;
1225
+ height: 90%;
1226
+ max-height: 700px;
1227
+ background: white;
1228
+ border-radius: 8px;
1229
+ overflow: hidden;
1230
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
1231
+ `;
1232
+ // Create iframe
1233
+ const iframe = document.createElement('iframe');
1234
+ iframe.src = authUrl;
1235
+ iframe.style.cssText = `
1236
+ width: 100%;
1237
+ height: 100%;
1238
+ border: none;
1239
+ `;
1240
+ iframe.allow = 'clipboard-write';
1241
+ iframeContainer.appendChild(iframe);
1242
+ modal.appendChild(closeButton);
1243
+ modal.appendChild(iframeContainer);
1244
+ document.body.appendChild(modal);
1245
+ }
1246
+ /**
1247
+ * Destroy authentication modal
1248
+ * @private
1249
+ */
1250
+ destroyAuthModal() {
1251
+ const modal = document.getElementById('playkit-external-auth-modal');
1252
+ if (modal) {
1253
+ modal.remove();
1254
+ }
1255
+ }
1256
+ /**
1257
+ * Exchange authorization code for access token
1258
+ * @private
1259
+ */
1260
+ async exchangeCodeForToken(code, codeVerifier, redirectUri) {
1261
+ const response = await fetch(`${this.baseURL}/api/external-auth/token`, {
1262
+ method: 'POST',
1263
+ headers: {
1264
+ 'Content-Type': 'application/json',
1265
+ },
1266
+ body: JSON.stringify({
1267
+ grant_type: 'authorization_code',
1268
+ code: code,
1269
+ code_verifier: codeVerifier,
1270
+ redirect_uri: redirectUri,
1271
+ }),
1272
+ });
1273
+ if (!response.ok) {
1274
+ const error = await response.json().catch(() => ({ error_description: 'Token exchange failed' }));
1275
+ throw new PlayKitError(error.error_description || 'Token exchange failed', 'TOKEN_EXCHANGE_FAILED', response.status);
1276
+ }
1277
+ const data = await response.json();
1278
+ return data.access_token;
1279
+ }
1280
+ /**
1281
+ * Clean up
1282
+ */
1283
+ destroy() {
1284
+ this.removeAllListeners();
1285
+ }
1286
+ }
1287
+
1055
1288
  /**
1056
1289
  * Authentication manager
1057
1290
  * Handles JWT exchange and token management
1058
1291
  */
1059
- const DEFAULT_BASE_URL$3 = 'https://playkit.agentlandlab.com';
1292
+ // @ts-ignore - replaced at build time
1293
+ const DEFAULT_BASE_URL$3 = "https://playkit.agentlandlab.com";
1060
1294
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
1061
1295
  class AuthManager extends EventEmitter {
1062
1296
  constructor(config) {
1063
1297
  super();
1064
1298
  this.authFlowManager = null;
1299
+ this.externalAuthFlowManager = null;
1065
1300
  this.config = config;
1066
1301
  this.storage = new TokenStorage();
1067
1302
  this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
@@ -1115,7 +1350,9 @@ class AuthManager extends EventEmitter {
1115
1350
  this.emit('unauthenticated');
1116
1351
  // Auto-start login flow in browser environment
1117
1352
  if (typeof window !== 'undefined') {
1118
- await this.startAuthFlow();
1353
+ // Default to external-auth if not specified
1354
+ const useExternalAuth = this.config.authMethod !== 'headless';
1355
+ await this.startAuthFlow(useExternalAuth);
1119
1356
  // If we reach here, authentication was successful
1120
1357
  // If it failed, startAuthFlow() will have thrown an error
1121
1358
  }
@@ -1126,27 +1363,53 @@ class AuthManager extends EventEmitter {
1126
1363
  }
1127
1364
  /**
1128
1365
  * Start the authentication flow UI
1366
+ *
1367
+ * @param useExternalAuth - Use external-auth OAuth flow instead of headless flow
1129
1368
  */
1130
- async startAuthFlow() {
1131
- var _a;
1132
- if (this.authFlowManager) {
1369
+ async startAuthFlow(useExternalAuth = false) {
1370
+ var _a, _b;
1371
+ if (this.authFlowManager || this.externalAuthFlowManager) {
1133
1372
  // Already in progress
1134
1373
  return;
1135
1374
  }
1136
1375
  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;
1376
+ if (useExternalAuth) {
1377
+ // Use external-auth OAuth popup flow
1378
+ this.externalAuthFlowManager = new ExternalAuthFlowManager(this.baseURL, this.config.gameId);
1379
+ // Get player token directly from external-auth flow
1380
+ const playerToken = await this.externalAuthFlowManager.startFlow();
1381
+ // Update auth state with the player token
1382
+ this.authState = {
1383
+ isAuthenticated: true,
1384
+ token: playerToken,
1385
+ tokenType: 'player',
1386
+ };
1387
+ // Save to storage
1388
+ await this.storage.saveAuthState(this.config.gameId, this.authState);
1389
+ await this.storage.saveSharedToken(playerToken);
1390
+ this.emit('authenticated', this.authState);
1391
+ // Clean up
1392
+ this.externalAuthFlowManager.destroy();
1393
+ this.externalAuthFlowManager = null;
1394
+ }
1395
+ else {
1396
+ // Use headless verification code flow
1397
+ this.authFlowManager = new AuthFlowManager(this.baseURL);
1398
+ // Get global token from auth flow
1399
+ const globalToken = await this.authFlowManager.startFlow();
1400
+ // Exchange for player token
1401
+ await this.exchangeJWT(globalToken);
1402
+ // Clean up
1403
+ this.authFlowManager.destroy();
1404
+ this.authFlowManager = null;
1405
+ }
1145
1406
  }
1146
1407
  catch (error) {
1147
1408
  // User canceled or error occurred
1148
1409
  (_a = this.authFlowManager) === null || _a === void 0 ? void 0 : _a.destroy();
1149
1410
  this.authFlowManager = null;
1411
+ (_b = this.externalAuthFlowManager) === null || _b === void 0 ? void 0 : _b.destroy();
1412
+ this.externalAuthFlowManager = null;
1150
1413
  // Re-emit error
1151
1414
  this.emit('error', error);
1152
1415
  throw error;
@@ -2787,5 +3050,5 @@ class PlayKitSDK extends EventEmitter {
2787
3050
  }
2788
3051
  }
2789
3052
 
2790
- export { AuthManager, ChatClient, ImageClient, NPCClient, PlayKitSDK, PlayerClient, RechargeManager, StreamParser, TokenStorage, PlayKitSDK as default };
3053
+ export { AuthFlowManager, AuthManager, ChatClient, ExternalAuthFlowManager, ImageClient, NPCClient, PlayKitSDK, PlayerClient, RechargeManager, StreamParser, TokenStorage, PlayKitSDK as default };
2791
3054
  //# sourceMappingURL=playkit-sdk.esm.js.map