@progalaxyelabs/ngx-stonescriptphp-client 1.10.0 → 1.11.1

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.
@@ -110,8 +110,8 @@ class TokenService {
110
110
  localStorage.removeItem(this.lsRefreshTokenKey);
111
111
  }
112
112
  /**
113
- * Check if there is a valid (non-empty) access token
114
- * @returns True if access token exists and is not empty
113
+ * Check if there is a non-empty access token.
114
+ * Token is treated as opaque validity is determined by the auth server.
115
115
  */
116
116
  hasValidAccessToken() {
117
117
  const token = this.getAccessToken();
@@ -246,6 +246,22 @@ class MyEnvironmentModel {
246
246
  * ```
247
247
  */
248
248
  customProviders;
249
+ /**
250
+ * Auth response field mapping.
251
+ * Defaults to StoneScriptPHP format: { status: 'ok', data: { access_token, user, ... } }
252
+ *
253
+ * For external auth servers that return flat responses like
254
+ * { access_token, identity, ... }, override with:
255
+ * ```typescript
256
+ * authResponseMap: {
257
+ * accessTokenPath: 'access_token',
258
+ * refreshTokenPath: 'refresh_token',
259
+ * userPath: 'identity',
260
+ * errorMessagePath: 'message'
261
+ * }
262
+ * ```
263
+ */
264
+ authResponseMap;
249
265
  /**
250
266
  * Branding configuration for auth components
251
267
  * Allows platforms to customize login/register pages without creating wrappers
@@ -795,6 +811,59 @@ class AuthService {
795
811
  }
796
812
  throw new Error('No platform API URL configured. Set apiUrl in environment config.');
797
813
  }
814
+ // ===== Auth Response Mapping Helpers =====
815
+ /** Default response map: StoneScriptPHP format */
816
+ get responseMap() {
817
+ return this.environment.authResponseMap ?? {
818
+ successPath: 'status',
819
+ successValue: 'ok',
820
+ accessTokenPath: 'data.access_token',
821
+ refreshTokenPath: 'data.refresh_token',
822
+ userPath: 'data.user',
823
+ errorMessagePath: 'message'
824
+ };
825
+ }
826
+ /** Resolve a dot-notation path on an object (e.g., 'data.access_token') */
827
+ resolvePath(obj, path) {
828
+ return path.split('.').reduce((o, key) => o?.[key], obj);
829
+ }
830
+ /** Check if an auth response indicates success */
831
+ isAuthSuccess(data) {
832
+ const map = this.responseMap;
833
+ if (map.successPath) {
834
+ return this.resolvePath(data, map.successPath) === (map.successValue ?? 'ok');
835
+ }
836
+ // No successPath configured — success = access token present
837
+ return !!this.resolvePath(data, map.accessTokenPath);
838
+ }
839
+ /** Extract access token from auth response */
840
+ getAccessToken(data) {
841
+ return this.resolvePath(data, this.responseMap.accessTokenPath);
842
+ }
843
+ /** Extract refresh token from auth response */
844
+ getRefreshToken(data) {
845
+ return this.resolvePath(data, this.responseMap.refreshTokenPath);
846
+ }
847
+ /** Extract user/identity object from auth response */
848
+ getUserFromResponse(data) {
849
+ return this.resolvePath(data, this.responseMap.userPath);
850
+ }
851
+ /** Extract error message from auth response */
852
+ getErrorMessage(data, fallback) {
853
+ const path = this.responseMap.errorMessagePath ?? 'message';
854
+ return this.resolvePath(data, path) || fallback;
855
+ }
856
+ /** Normalize a user/identity object into the standard User shape */
857
+ normalizeUser(raw) {
858
+ return {
859
+ user_id: raw.user_id ?? (raw.id ? this.hashUUID(raw.id) : 0),
860
+ id: raw.id ?? String(raw.user_id),
861
+ email: raw.email,
862
+ display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
863
+ photo_url: raw.photo_url,
864
+ is_email_verified: raw.is_email_verified ?? false
865
+ };
866
+ }
798
867
  /**
799
868
  * Hash UUID to numeric ID for backward compatibility
800
869
  * Converts UUID string to a consistent numeric ID for legacy code
@@ -858,7 +927,7 @@ class AuthService {
858
927
  const response = await fetch(`${accountsUrl}/api/auth/login`, {
859
928
  method: 'POST',
860
929
  headers: { 'Content-Type': 'application/json' },
861
- credentials: 'include', // Include cookies for refresh token
930
+ credentials: 'include',
862
931
  body: JSON.stringify({
863
932
  email,
864
933
  password,
@@ -866,24 +935,21 @@ class AuthService {
866
935
  })
867
936
  });
868
937
  const data = await response.json();
869
- if (data.success && data.access_token) {
870
- this.tokens.setAccessToken(data.access_token);
938
+ if (this.isAuthSuccess(data)) {
939
+ const accessToken = this.getAccessToken(data);
940
+ this.tokens.setAccessToken(accessToken);
871
941
  this.signinStatus.setSigninStatus(true);
872
- // Normalize user object to handle both response formats
873
- const normalizedUser = {
874
- user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
875
- id: data.user.id ?? String(data.user.user_id),
876
- email: data.user.email,
877
- display_name: data.user.display_name ?? data.user.email.split('@')[0],
878
- photo_url: data.user.photo_url,
879
- is_email_verified: data.user.is_email_verified ?? false
880
- };
881
- this.updateUser(normalizedUser);
882
- return { success: true, user: normalizedUser };
942
+ const rawUser = this.getUserFromResponse(data);
943
+ if (rawUser) {
944
+ const normalizedUser = this.normalizeUser(rawUser);
945
+ this.updateUser(normalizedUser);
946
+ return { success: true, user: normalizedUser };
947
+ }
948
+ return { success: true };
883
949
  }
884
950
  return {
885
951
  success: false,
886
- message: data.message || 'Invalid credentials'
952
+ message: this.getErrorMessage(data, 'Invalid credentials')
887
953
  };
888
954
  }
889
955
  catch (error) {
@@ -979,16 +1045,11 @@ class AuthService {
979
1045
  if (event.data.type === 'oauth_success') {
980
1046
  this.tokens.setAccessToken(event.data.access_token);
981
1047
  this.signinStatus.setSigninStatus(true);
982
- // Normalize user object to handle both response formats
983
- const normalizedUser = {
984
- user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
985
- id: event.data.user.id ?? String(event.data.user.user_id),
986
- email: event.data.user.email,
987
- display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
988
- photo_url: event.data.user.photo_url,
989
- is_email_verified: event.data.user.is_email_verified ?? false
990
- };
991
- this.updateUser(normalizedUser);
1048
+ const rawUser = event.data.user || this.getUserFromResponse(event.data);
1049
+ const normalizedUser = rawUser ? this.normalizeUser(rawUser) : undefined;
1050
+ if (normalizedUser) {
1051
+ this.updateUser(normalizedUser);
1052
+ }
992
1053
  window.removeEventListener('message', messageHandler);
993
1054
  popup.close();
994
1055
  resolve({
@@ -1041,28 +1102,25 @@ class AuthService {
1041
1102
  })
1042
1103
  });
1043
1104
  const data = await response.json();
1044
- if (data.success && data.access_token) {
1045
- this.tokens.setAccessToken(data.access_token);
1105
+ if (this.isAuthSuccess(data)) {
1106
+ const accessToken = this.getAccessToken(data);
1107
+ this.tokens.setAccessToken(accessToken);
1046
1108
  this.signinStatus.setSigninStatus(true);
1047
- // Normalize user object to handle both response formats
1048
- const normalizedUser = {
1049
- user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
1050
- id: data.user.id ?? String(data.user.user_id),
1051
- email: data.user.email,
1052
- display_name: data.user.display_name ?? data.user.email.split('@')[0],
1053
- photo_url: data.user.photo_url,
1054
- is_email_verified: data.user.is_email_verified ?? false
1055
- };
1056
- this.updateUser(normalizedUser);
1057
- return {
1058
- success: true,
1059
- user: normalizedUser,
1060
- message: data.needs_verification ? 'Please verify your email' : undefined
1061
- };
1109
+ const rawUser = this.getUserFromResponse(data);
1110
+ if (rawUser) {
1111
+ const normalizedUser = this.normalizeUser(rawUser);
1112
+ this.updateUser(normalizedUser);
1113
+ return {
1114
+ success: true,
1115
+ user: normalizedUser,
1116
+ message: data.needs_verification ? 'Please verify your email' : undefined
1117
+ };
1118
+ }
1119
+ return { success: true };
1062
1120
  }
1063
1121
  return {
1064
1122
  success: false,
1065
- message: data.message || 'Registration failed'
1123
+ message: this.getErrorMessage(data, 'Registration failed')
1066
1124
  };
1067
1125
  }
1068
1126
  catch (error) {
@@ -1119,30 +1177,33 @@ class AuthService {
1119
1177
  credentials: 'include'
1120
1178
  });
1121
1179
  if (!response.ok) {
1180
+ // Refresh failed (expired, revoked, wrong keypair) — clear stale tokens
1181
+ // so the user gets a clean login page, not a broken retry loop
1182
+ this.tokens.clear();
1183
+ this.updateUser(null);
1122
1184
  this.signinStatus.setSigninStatus(false);
1123
1185
  return false;
1124
1186
  }
1125
1187
  const data = await response.json();
1126
- if (data.access_token) {
1127
- this.tokens.setAccessToken(data.access_token);
1128
- // Normalize user object to handle both response formats
1129
- if (data.user) {
1130
- const normalizedUser = {
1131
- user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
1132
- id: data.user.id ?? String(data.user.user_id),
1133
- email: data.user.email,
1134
- display_name: data.user.display_name ?? data.user.email.split('@')[0],
1135
- photo_url: data.user.photo_url,
1136
- is_email_verified: data.user.is_email_verified ?? false
1137
- };
1138
- this.updateUser(normalizedUser);
1188
+ const accessToken = this.getAccessToken(data) ?? data.access_token;
1189
+ if (accessToken) {
1190
+ this.tokens.setAccessToken(accessToken);
1191
+ const rawUser = this.getUserFromResponse(data) ?? data.user;
1192
+ if (rawUser) {
1193
+ this.updateUser(this.normalizeUser(rawUser));
1139
1194
  }
1140
1195
  this.signinStatus.setSigninStatus(true);
1141
1196
  return true;
1142
1197
  }
1198
+ // Response OK but no token — clear stale state
1199
+ this.tokens.clear();
1200
+ this.updateUser(null);
1143
1201
  return false;
1144
1202
  }
1145
1203
  catch (error) {
1204
+ // Network error — clear stale state to avoid retry loops
1205
+ this.tokens.clear();
1206
+ this.updateUser(null);
1146
1207
  this.signinStatus.setSigninStatus(false);
1147
1208
  return false;
1148
1209
  }
@@ -1187,20 +1248,15 @@ class AuthService {
1187
1248
  })
1188
1249
  });
1189
1250
  const result = await response.json();
1190
- if (result.success && result.access_token) {
1191
- this.tokens.setAccessToken(result.access_token);
1192
- this.signinStatus.setSigninStatus(true);
1193
- if (result.user) {
1194
- // Normalize user object to handle both response formats
1195
- const normalizedUser = {
1196
- user_id: result.user.user_id ?? (result.user.id ? this.hashUUID(result.user.id) : 0),
1197
- id: result.user.id ?? String(result.user.user_id),
1198
- email: result.user.email,
1199
- display_name: result.user.display_name ?? result.user.email.split('@')[0],
1200
- photo_url: result.user.photo_url,
1201
- is_email_verified: result.user.is_email_verified ?? false
1202
- };
1203
- this.updateUser(normalizedUser);
1251
+ if (this.isAuthSuccess(result)) {
1252
+ const accessToken = this.getAccessToken(result);
1253
+ if (accessToken) {
1254
+ this.tokens.setAccessToken(accessToken);
1255
+ this.signinStatus.setSigninStatus(true);
1256
+ }
1257
+ const rawUser = this.getUserFromResponse(result);
1258
+ if (rawUser) {
1259
+ this.updateUser(this.normalizeUser(rawUser));
1204
1260
  }
1205
1261
  }
1206
1262
  return result;
@@ -1245,22 +1301,13 @@ class AuthService {
1245
1301
  return;
1246
1302
  }
1247
1303
  if (event.data.type === 'tenant_register_success') {
1248
- // Set tokens and user
1249
1304
  if (event.data.access_token) {
1250
1305
  this.tokens.setAccessToken(event.data.access_token);
1251
1306
  this.signinStatus.setSigninStatus(true);
1252
1307
  }
1253
- if (event.data.user) {
1254
- // Normalize user object to handle both response formats
1255
- const normalizedUser = {
1256
- user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
1257
- id: event.data.user.id ?? String(event.data.user.user_id),
1258
- email: event.data.user.email,
1259
- display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
1260
- photo_url: event.data.user.photo_url,
1261
- is_email_verified: event.data.user.is_email_verified ?? false
1262
- };
1263
- this.updateUser(normalizedUser);
1308
+ const rawUser = event.data.user || this.getUserFromResponse(event.data);
1309
+ if (rawUser) {
1310
+ this.updateUser(this.normalizeUser(rawUser));
1264
1311
  }
1265
1312
  window.removeEventListener('message', messageHandler);
1266
1313
  popup.close();
@@ -1336,16 +1383,17 @@ class AuthService {
1336
1383
  body: JSON.stringify({ tenant_id: tenantId })
1337
1384
  });
1338
1385
  const data = await response.json();
1339
- if (data.success && data.access_token) {
1340
- this.tokens.setAccessToken(data.access_token);
1386
+ if (this.isAuthSuccess(data)) {
1387
+ const accessToken = this.getAccessToken(data);
1388
+ this.tokens.setAccessToken(accessToken);
1341
1389
  return {
1342
1390
  success: true,
1343
- access_token: data.access_token
1391
+ access_token: accessToken
1344
1392
  };
1345
1393
  }
1346
1394
  return {
1347
1395
  success: false,
1348
- message: data.message || 'Failed to select tenant'
1396
+ message: this.getErrorMessage(data, 'Failed to select tenant')
1349
1397
  };
1350
1398
  }
1351
1399
  catch (error) {