@bounded-sh/core 0.0.20 → 0.0.22
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.
- package/dist/client/config.d.ts +5 -4
- package/dist/client/subscription-v2.d.ts +3 -3
- package/dist/index.js +102 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +102 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -10,8 +10,8 @@ let clientConfig = {
|
|
|
10
10
|
// User configured settings
|
|
11
11
|
name: '',
|
|
12
12
|
logoUrl: '',
|
|
13
|
-
// Bounded production is the
|
|
14
|
-
//
|
|
13
|
+
// Bounded production is the endpoint default. Apps still choose one explicit
|
|
14
|
+
// auth method at init time. Pass `network: 'bounded-staging'` to target staging.
|
|
15
15
|
network: 'bounded-production',
|
|
16
16
|
wsApiUrl: 'wss://realtime.bounded.sh',
|
|
17
17
|
apiUrl: 'https://realtime.bounded.sh',
|
|
@@ -19,14 +19,15 @@ let clientConfig = {
|
|
|
19
19
|
humanAuthApiUrl: 'https://auth.bounded.sh',
|
|
20
20
|
functionsUrl: 'https://functions.bounded.sh',
|
|
21
21
|
appId: '',
|
|
22
|
-
//
|
|
23
|
-
// for
|
|
22
|
+
// No hidden auth fallback: browser clients must pass authMethod explicitly
|
|
23
|
+
// (for example 'email', 'guest', 'phantom', 'privy', or 'privy-expo').
|
|
24
|
+
// Hosted OAuth/social uses loginWithRedirect/loginWithPopup with authMethod:'email'.
|
|
24
25
|
// Text OTP is off by default and uses hosted/headless text helpers only when
|
|
25
26
|
// Bounded explicitly enables it for the issuer. For
|
|
26
27
|
// crypto/onchain wallet login use authMethod:'phantom' (Solana / Phantom), or
|
|
27
28
|
// signInAnonymously() for zero-friction 'guest' accounts. ('wallet' is an
|
|
28
29
|
// unimplemented stub; don't use.)
|
|
29
|
-
authMethod: '
|
|
30
|
+
authMethod: 'none',
|
|
30
31
|
chain: '',
|
|
31
32
|
rpcUrl: '',
|
|
32
33
|
skipBackendInit: true,
|
|
@@ -92,10 +93,14 @@ function init(newConfig) {
|
|
|
92
93
|
}
|
|
93
94
|
// Bounded is client-driven: defaults are Bounded production, `network`
|
|
94
95
|
// switches the whole endpoint set (e.g. 'bounded-staging'), and anything
|
|
95
|
-
// passed explicitly wins. No `/config` round-trip
|
|
96
|
-
//
|
|
96
|
+
// passed explicitly wins. No `/config` round-trip; browser SDKs still pass
|
|
97
|
+
// one explicit authMethod so there is no hidden auth-provider fallback.
|
|
97
98
|
// defaults (bounded-production) < network preset < explicit newConfig
|
|
98
|
-
|
|
99
|
+
if (newConfig.network !== undefined && !(newConfig.network in BOUNDED_NETWORKS)) {
|
|
100
|
+
reject(new Error(`Unsupported Bounded network "${String(newConfig.network)}". Expected bounded, bounded-staging, or bounded-production.`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const preset = newConfig.network ? BOUNDED_NETWORKS[newConfig.network] : {};
|
|
99
104
|
clientConfig = Object.assign(Object.assign(Object.assign({}, clientConfig), preset), newConfig);
|
|
100
105
|
isInitialized = true;
|
|
101
106
|
resolve();
|
|
@@ -5410,6 +5415,48 @@ function getCacheKey(path, prompt, shape, limit, cursor, filter, identity) {
|
|
|
5410
5415
|
function principalFromIdToken(idToken) {
|
|
5411
5416
|
return idToken ? `t${hashForKey(idToken)}` : null;
|
|
5412
5417
|
}
|
|
5418
|
+
function livePrincipalFromIdToken(idToken) {
|
|
5419
|
+
if (!idToken)
|
|
5420
|
+
return null;
|
|
5421
|
+
try {
|
|
5422
|
+
const payload = JSON.parse(decodeBase64Url(idToken.split('.')[1]));
|
|
5423
|
+
const userId = payload['custom:userId'];
|
|
5424
|
+
if (typeof userId === 'string' && userId.length > 0)
|
|
5425
|
+
return userId;
|
|
5426
|
+
const walletAddress = payload['custom:walletAddress'];
|
|
5427
|
+
if (typeof walletAddress === 'string' && walletAddress.length > 0)
|
|
5428
|
+
return walletAddress;
|
|
5429
|
+
const subject = payload.sub;
|
|
5430
|
+
return typeof subject === 'string' && subject.length > 0 ? subject : null;
|
|
5431
|
+
}
|
|
5432
|
+
catch (_a) {
|
|
5433
|
+
return null;
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
function authSnapshotFromToken(idToken) {
|
|
5437
|
+
return {
|
|
5438
|
+
tokenFingerprint: principalFromIdToken(idToken),
|
|
5439
|
+
principal: livePrincipalFromIdToken(idToken),
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
function connectionMatchesAuthToken(connection, idToken) {
|
|
5443
|
+
const current = authSnapshotFromToken(idToken);
|
|
5444
|
+
if (!current.tokenFingerprint) {
|
|
5445
|
+
return connection.authTokenFingerprint === null && connection.authPrincipal === null;
|
|
5446
|
+
}
|
|
5447
|
+
if (connection.authTokenFingerprint && connection.authTokenFingerprint === current.tokenFingerprint) {
|
|
5448
|
+
return true;
|
|
5449
|
+
}
|
|
5450
|
+
return !!connection.authPrincipal && !!current.principal && connection.authPrincipal === current.principal;
|
|
5451
|
+
}
|
|
5452
|
+
async function canSendAuthenticatedLiveIntent(connection, isServer) {
|
|
5453
|
+
const currentToken = await getIdToken(isServer);
|
|
5454
|
+
if (!currentToken)
|
|
5455
|
+
return false;
|
|
5456
|
+
if (!connection.authTokenFingerprint && !connection.authPrincipal)
|
|
5457
|
+
return false;
|
|
5458
|
+
return connectionMatchesAuthToken(connection, currentToken);
|
|
5459
|
+
}
|
|
5413
5460
|
async function getSubscriptionIdentity(effectiveAppId, isServer, overrides) {
|
|
5414
5461
|
// Per-subscription wallet override (server WalletClient.subscribe): key by
|
|
5415
5462
|
// the wallet's own opaque token material, mirroring getReadPrincipalKey. Do
|
|
@@ -5485,6 +5532,9 @@ function rememberAmbientAuthFailure(error) {
|
|
|
5485
5532
|
function failConnectionAuth(connection, error) {
|
|
5486
5533
|
connection.authFailure = error;
|
|
5487
5534
|
connection.pendingAuthToken = null;
|
|
5535
|
+
connection.pendingAuthSnapshot = null;
|
|
5536
|
+
connection.authTokenFingerprint = null;
|
|
5537
|
+
connection.authPrincipal = null;
|
|
5488
5538
|
connection.isConnecting = false;
|
|
5489
5539
|
connection.isConnected = false;
|
|
5490
5540
|
connection.isAuthenticating = false;
|
|
@@ -5703,16 +5753,37 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5703
5753
|
if (connection.authFailure) {
|
|
5704
5754
|
throw connection.authFailure;
|
|
5705
5755
|
}
|
|
5756
|
+
let currentAuthToken;
|
|
5706
5757
|
try {
|
|
5707
|
-
await getConnectionAuthToken(isServer, authTokenProvider);
|
|
5758
|
+
currentAuthToken = await getConnectionAuthToken(isServer, authTokenProvider);
|
|
5708
5759
|
}
|
|
5709
5760
|
catch (error) {
|
|
5710
5761
|
const authError = normalizeAuthExpiredError(error, 'Authentication expired and refresh failed');
|
|
5711
5762
|
failConnectionAuth(connection, authError);
|
|
5712
5763
|
throw authError;
|
|
5713
5764
|
}
|
|
5765
|
+
if (connection.isConnected &&
|
|
5766
|
+
!connection.isAuthenticating &&
|
|
5767
|
+
!connectionMatchesAuthToken(connection, currentAuthToken)) {
|
|
5768
|
+
if (connection.tokenRefreshTimer) {
|
|
5769
|
+
clearInterval(connection.tokenRefreshTimer);
|
|
5770
|
+
connection.tokenRefreshTimer = null;
|
|
5771
|
+
}
|
|
5772
|
+
try {
|
|
5773
|
+
connection.ws.close();
|
|
5774
|
+
}
|
|
5775
|
+
catch (_a) {
|
|
5776
|
+
// Already closing.
|
|
5777
|
+
}
|
|
5778
|
+
connection.ws = null;
|
|
5779
|
+
connection.isConnected = false;
|
|
5780
|
+
connections.delete(connection.key);
|
|
5781
|
+
}
|
|
5782
|
+
else {
|
|
5783
|
+
connection.authFailure = null;
|
|
5784
|
+
return connection;
|
|
5785
|
+
}
|
|
5714
5786
|
connection.authFailure = null;
|
|
5715
|
-
return connection;
|
|
5716
5787
|
}
|
|
5717
5788
|
let initialAuthToken = await getConnectionAuthToken(isServer, authTokenProvider);
|
|
5718
5789
|
// Create new connection
|
|
@@ -5730,6 +5801,9 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5730
5801
|
routePath: roomKey ? routePath : undefined,
|
|
5731
5802
|
authTokenProvider,
|
|
5732
5803
|
pendingAuthToken: null,
|
|
5804
|
+
pendingAuthSnapshot: null,
|
|
5805
|
+
authTokenFingerprint: null,
|
|
5806
|
+
authPrincipal: null,
|
|
5733
5807
|
tokenRefreshTimer: null,
|
|
5734
5808
|
consecutiveAuthFailures: 0,
|
|
5735
5809
|
authFailure: null,
|
|
@@ -5767,6 +5841,7 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5767
5841
|
throw authError;
|
|
5768
5842
|
}
|
|
5769
5843
|
connection.pendingAuthToken = authToken || null;
|
|
5844
|
+
connection.pendingAuthSnapshot = authSnapshotFromToken(authToken);
|
|
5770
5845
|
if (authToken) {
|
|
5771
5846
|
// Successful token acquisition — reset failure counter
|
|
5772
5847
|
connection.consecutiveAuthFailures = 0;
|
|
@@ -5806,6 +5881,8 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5806
5881
|
}
|
|
5807
5882
|
else {
|
|
5808
5883
|
connection.isAuthenticating = false;
|
|
5884
|
+
connection.authTokenFingerprint = null;
|
|
5885
|
+
connection.authPrincipal = null;
|
|
5809
5886
|
replaySubscriptions(connection);
|
|
5810
5887
|
}
|
|
5811
5888
|
});
|
|
@@ -5845,10 +5922,12 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5845
5922
|
return connection;
|
|
5846
5923
|
}
|
|
5847
5924
|
function handleServerMessage(connection, message) {
|
|
5848
|
-
var _a;
|
|
5925
|
+
var _a, _b, _c, _d, _e;
|
|
5849
5926
|
switch (message.type) {
|
|
5850
5927
|
case 'authenticated': {
|
|
5851
5928
|
connection.isAuthenticating = false;
|
|
5929
|
+
connection.authTokenFingerprint = (_b = (_a = connection.pendingAuthSnapshot) === null || _a === void 0 ? void 0 : _a.tokenFingerprint) !== null && _b !== void 0 ? _b : null;
|
|
5930
|
+
connection.authPrincipal = (_d = (_c = connection.pendingAuthSnapshot) === null || _c === void 0 ? void 0 : _c.principal) !== null && _d !== void 0 ? _d : null;
|
|
5852
5931
|
replaySubscriptions(connection);
|
|
5853
5932
|
break;
|
|
5854
5933
|
}
|
|
@@ -5919,7 +5998,7 @@ function handleServerMessage(connection, message) {
|
|
|
5919
5998
|
connection.pendingRequests.delete(message.requestId);
|
|
5920
5999
|
clearTimeout(pendingSet.timer);
|
|
5921
6000
|
if (message.statusCode >= 400) {
|
|
5922
|
-
pendingSet.reject(new Error(((
|
|
6001
|
+
pendingSet.reject(new Error(((_e = message.body) === null || _e === void 0 ? void 0 : _e.message) || `Set failed with status ${message.statusCode}`));
|
|
5923
6002
|
}
|
|
5924
6003
|
else {
|
|
5925
6004
|
pendingSet.resolve(message.body);
|
|
@@ -6447,13 +6526,16 @@ function hasActiveConnection() {
|
|
|
6447
6526
|
* room authority — the connection is already routed to the room DO, so the
|
|
6448
6527
|
* client never names a destination.
|
|
6449
6528
|
*/
|
|
6450
|
-
function wsIntent(appId, roomRoutePath, intent) {
|
|
6529
|
+
async function wsIntent(appId, roomRoutePath, intent, isServer) {
|
|
6451
6530
|
const roomKey = roomKeyFromRoutePath(roomRoutePath);
|
|
6452
6531
|
const connKey = roomKey ? `${appId}#room#${roomKey}` : appId;
|
|
6453
6532
|
const connection = connections.get(connKey);
|
|
6454
6533
|
if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN || connection.isAuthenticating) {
|
|
6455
6534
|
return false;
|
|
6456
6535
|
}
|
|
6536
|
+
if (!(await canSendAuthenticatedLiveIntent(connection, isServer))) {
|
|
6537
|
+
return false;
|
|
6538
|
+
}
|
|
6457
6539
|
try {
|
|
6458
6540
|
connection.ws.send(JSON.stringify({ type: 'intent', intent }));
|
|
6459
6541
|
return true;
|
|
@@ -6470,13 +6552,16 @@ function wsIntent(appId, roomRoutePath, intent) {
|
|
|
6470
6552
|
* Promise that resolves on ack / rejects on error, or `undefined` if no live
|
|
6471
6553
|
* socket exists yet (caller falls back to HTTP, which also surfaces errors).
|
|
6472
6554
|
*/
|
|
6473
|
-
function wsIntentReliable(appId, roomRoutePath, intent) {
|
|
6555
|
+
async function wsIntentReliable(appId, roomRoutePath, intent, isServer) {
|
|
6474
6556
|
const roomKey = roomKeyFromRoutePath(roomRoutePath);
|
|
6475
6557
|
const connKey = roomKey ? `${appId}#room#${roomKey}` : appId;
|
|
6476
6558
|
const connection = connections.get(connKey);
|
|
6477
6559
|
if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN || connection.isAuthenticating) {
|
|
6478
6560
|
return undefined;
|
|
6479
6561
|
}
|
|
6562
|
+
if (!(await canSendAuthenticatedLiveIntent(connection, isServer))) {
|
|
6563
|
+
return undefined;
|
|
6564
|
+
}
|
|
6480
6565
|
const requestId = generateRequestId();
|
|
6481
6566
|
return new Promise((resolve, reject) => {
|
|
6482
6567
|
const timer = setTimeout(() => {
|
|
@@ -6888,13 +6973,13 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
6888
6973
|
const hasAuthOverride = !!((_a = opts._overrides) === null || _a === void 0 ? void 0 : _a._getAuthHeaders);
|
|
6889
6974
|
if (!hasAuthOverride) {
|
|
6890
6975
|
if (opts.fireAndForget) {
|
|
6891
|
-
if (wsIntent(config.appId, normalizedRoomPath, intent))
|
|
6976
|
+
if (await wsIntent(config.appId, normalizedRoomPath, intent, config.isServer))
|
|
6892
6977
|
return { ok: true };
|
|
6893
6978
|
}
|
|
6894
6979
|
else {
|
|
6895
|
-
const ack = wsIntentReliable(config.appId, normalizedRoomPath, intent);
|
|
6980
|
+
const ack = await wsIntentReliable(config.appId, normalizedRoomPath, intent, config.isServer);
|
|
6896
6981
|
if (ack)
|
|
6897
|
-
return
|
|
6982
|
+
return ack;
|
|
6898
6983
|
}
|
|
6899
6984
|
}
|
|
6900
6985
|
const base = realtimeHttpBase(config.wsApiUrl);
|