@yeying-community/web3-bs 1.0.2 → 1.0.4

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.
@@ -6,11 +6,43 @@
6
6
 
7
7
  const YEYING_RDNS = 'io.github.yeying';
8
8
  const DEFAULT_TIMEOUT = 1000;
9
+ const DEFAULT_ACCOUNT_STORAGE_KEY = 'yeying:last_account';
9
10
  function getWindowEthereum() {
10
11
  if (typeof window === 'undefined')
11
12
  return null;
12
13
  return window.ethereum || null;
13
14
  }
15
+ function readStoredAccount(storageKey) {
16
+ if (typeof localStorage === 'undefined')
17
+ return null;
18
+ try {
19
+ return localStorage.getItem(storageKey);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function writeStoredAccount(storageKey, account) {
26
+ if (typeof localStorage === 'undefined')
27
+ return;
28
+ try {
29
+ if (account) {
30
+ localStorage.setItem(storageKey, account);
31
+ }
32
+ else {
33
+ localStorage.removeItem(storageKey);
34
+ }
35
+ }
36
+ catch {
37
+ // ignore storage errors
38
+ }
39
+ }
40
+ function selectPreferredAccount(accounts, stored, preferStored) {
41
+ if (preferStored && stored && accounts.includes(stored)) {
42
+ return stored;
43
+ }
44
+ return accounts[0] || null;
45
+ }
14
46
  function isYeYingProvider(provider, info) {
15
47
  if (!provider)
16
48
  return false;
@@ -116,6 +148,29 @@
116
148
  const chainId = (await p.request({ method: 'eth_chainId' }));
117
149
  return typeof chainId === 'string' ? chainId : null;
118
150
  }
151
+ async function getPreferredAccount(options = {}) {
152
+ const provider = options.provider || (await requireProvider());
153
+ const storageKey = options.storageKey || DEFAULT_ACCOUNT_STORAGE_KEY;
154
+ const preferStored = options.preferStored !== false;
155
+ let accounts = await getAccounts(provider);
156
+ if (accounts.length === 0 && options.autoConnect) {
157
+ accounts = await requestAccounts({ provider });
158
+ }
159
+ const stored = readStoredAccount(storageKey);
160
+ const account = selectPreferredAccount(accounts, stored, preferStored);
161
+ writeStoredAccount(storageKey, account);
162
+ return { account, accounts };
163
+ }
164
+ function watchAccounts(provider, handler, options = {}) {
165
+ const storageKey = options.storageKey || DEFAULT_ACCOUNT_STORAGE_KEY;
166
+ const preferStored = options.preferStored !== false;
167
+ return onAccountsChanged(provider, (accounts) => {
168
+ const stored = readStoredAccount(storageKey);
169
+ const account = selectPreferredAccount(accounts, stored, preferStored);
170
+ writeStoredAccount(storageKey, account);
171
+ handler({ account, accounts });
172
+ });
173
+ }
119
174
  async function getBalance(provider, address, blockTag = 'latest') {
120
175
  const p = provider || (await requireProvider());
121
176
  let target = address;
@@ -643,6 +698,25 @@
643
698
  const record = await readSessionRecord(id);
644
699
  return record?.root || null;
645
700
  }
701
+ function capsEqual(a, b) {
702
+ return JSON.stringify(a || []) === JSON.stringify(b || []);
703
+ }
704
+ function isRootExpired(root, nowMs) {
705
+ return Boolean(root.exp && nowMs > root.exp);
706
+ }
707
+ async function getOrCreateUcanRoot(options) {
708
+ const provider = options.provider || (await requireProvider());
709
+ const session = options.session || (await createUcanSession({ id: options.sessionId, provider }));
710
+ const nowMs = Date.now();
711
+ const stored = await getStoredUcanRoot(session.id);
712
+ if (stored &&
713
+ (!stored.aud || stored.aud === session.did) &&
714
+ capsEqual(stored.cap, options.capabilities) &&
715
+ !isRootExpired(stored, nowMs)) {
716
+ return stored;
717
+ }
718
+ return await createRootUcan({ ...options, provider, session });
719
+ }
646
720
  function buildUcanStatement(payload) {
647
721
  return `UCAN-AUTH ${JSON.stringify(payload)}`;
648
722
  }
@@ -667,7 +741,15 @@
667
741
  async function resolveAddress(provider, address) {
668
742
  if (address)
669
743
  return address;
670
- const accounts = await getAccounts(provider);
744
+ let accounts = await getAccounts(provider);
745
+ if (!accounts[0]) {
746
+ const requested = (await provider.request({
747
+ method: 'eth_requestAccounts',
748
+ }));
749
+ if (Array.isArray(requested)) {
750
+ accounts = requested;
751
+ }
752
+ }
671
753
  if (!accounts[0])
672
754
  throw new Error('No account available');
673
755
  return accounts[0];
@@ -687,8 +769,8 @@
687
769
  const session = options.session || (await createUcanSession({ id: options.sessionId, provider }));
688
770
  const address = await resolveAddress(provider, options.address);
689
771
  const chainId = options.chainId || (await getChainId(provider)) || '1';
690
- const domain = options.domain || (typeof window !== 'undefined' ? window.location.host : 'localhost');
691
- const uri = options.uri || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost');
772
+ const domain = options.domain || (typeof window !== 'undefined' ? window.location.host : '127.0.0.1');
773
+ const uri = options.uri || (typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1');
692
774
  const nonce = options.nonce || randomNonce(8);
693
775
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
694
776
  const nbf = options.notBeforeMs;
@@ -931,6 +1013,27 @@
931
1013
  async createDirectory(path) {
932
1014
  return await this.request('MKCOL', path);
933
1015
  }
1016
+ async ensureDirectory(path) {
1017
+ if (!path || path === '/')
1018
+ return;
1019
+ const segments = path.split('/').filter(Boolean);
1020
+ if (segments.length === 0)
1021
+ return;
1022
+ let current = '';
1023
+ for (const segment of segments) {
1024
+ current = `${current}/${segment}`;
1025
+ const res = await this.fetcher(this.buildUrl(current), {
1026
+ method: 'MKCOL',
1027
+ headers: this.buildHeaders(),
1028
+ credentials: this.credentials,
1029
+ });
1030
+ if (res.ok)
1031
+ continue;
1032
+ if (res.status === 405)
1033
+ continue;
1034
+ throw new Error(`WebDAV MKCOL ${current} failed: ${res.status} ${res.statusText}`);
1035
+ }
1036
+ }
934
1037
  async remove(path) {
935
1038
  return await this.request('DELETE', path);
936
1039
  }
@@ -1014,6 +1117,198 @@
1014
1117
  return new WebDavClient(options);
1015
1118
  }
1016
1119
 
1120
+ const tokenCache = new Map();
1121
+ const TOKEN_SKEW_MS = 5000;
1122
+ function normalizeAppDir(path) {
1123
+ const trimmed = path.trim();
1124
+ if (!trimmed)
1125
+ return '/';
1126
+ let next = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
1127
+ next = next.replace(/\/+$/, '');
1128
+ return next || '/';
1129
+ }
1130
+ function sanitizeAppId(appId) {
1131
+ return appId.trim().replace(/[^a-zA-Z0-9._-]/g, '-');
1132
+ }
1133
+ function resolveAppDir(options) {
1134
+ if (options.appDir) {
1135
+ return normalizeAppDir(options.appDir);
1136
+ }
1137
+ if (options.appId) {
1138
+ return normalizeAppDir(`/apps/${sanitizeAppId(options.appId)}`);
1139
+ }
1140
+ return undefined;
1141
+ }
1142
+ function buildCapsKey(caps) {
1143
+ return JSON.stringify(caps || []);
1144
+ }
1145
+ function buildTokenCacheKey(issuer, audience, caps) {
1146
+ return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
1147
+ }
1148
+ function isTokenValid(entry, nowMs) {
1149
+ if (!entry.exp)
1150
+ return false;
1151
+ if (entry.nbf && nowMs < entry.nbf)
1152
+ return false;
1153
+ return entry.exp - TOKEN_SKEW_MS > nowMs;
1154
+ }
1155
+ function decodeBase64Url(input) {
1156
+ if (!input)
1157
+ return null;
1158
+ const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
1159
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
1160
+ try {
1161
+ if (typeof atob === 'function') {
1162
+ return atob(padded);
1163
+ }
1164
+ }
1165
+ catch {
1166
+ // ignore
1167
+ }
1168
+ try {
1169
+ const nodeBuffer = globalThis.Buffer;
1170
+ if (nodeBuffer) {
1171
+ return nodeBuffer.from(padded, 'base64').toString('utf8');
1172
+ }
1173
+ }
1174
+ catch {
1175
+ return null;
1176
+ }
1177
+ return null;
1178
+ }
1179
+ function decodeUcanPayload(token) {
1180
+ const parts = token.split('.');
1181
+ if (parts.length < 2)
1182
+ return null;
1183
+ const decoded = decodeBase64Url(parts[1]);
1184
+ if (!decoded)
1185
+ return null;
1186
+ try {
1187
+ return JSON.parse(decoded);
1188
+ }
1189
+ catch {
1190
+ return null;
1191
+ }
1192
+ }
1193
+ async function getCachedInvocationToken(options) {
1194
+ const cacheKey = buildTokenCacheKey(options.issuer, options.audience, options.capabilities);
1195
+ const cached = tokenCache.get(cacheKey);
1196
+ const nowMs = Date.now();
1197
+ if (cached && isTokenValid(cached, nowMs)) {
1198
+ return cached.token;
1199
+ }
1200
+ const token = await createInvocationUcan({
1201
+ issuer: options.issuer,
1202
+ audience: options.audience,
1203
+ capabilities: options.capabilities,
1204
+ proofs: options.proofs,
1205
+ expiresInMs: options.expiresInMs,
1206
+ notBeforeMs: options.notBeforeMs,
1207
+ });
1208
+ const payload = decodeUcanPayload(token);
1209
+ if (payload && typeof payload.exp === 'number') {
1210
+ tokenCache.set(cacheKey, {
1211
+ token,
1212
+ exp: payload.exp,
1213
+ nbf: payload.nbf,
1214
+ });
1215
+ }
1216
+ return token;
1217
+ }
1218
+ async function initWebDavStorage(options) {
1219
+ const caps = options.capabilities || options.root?.cap;
1220
+ if (!caps || caps.length === 0) {
1221
+ throw new Error('Missing UCAN capabilities for WebDAV');
1222
+ }
1223
+ const needsProvider = !options.session || !options.root;
1224
+ const provider = options.provider || (needsProvider ? await requireProvider() : undefined);
1225
+ const session = options.session ||
1226
+ (await createUcanSession({
1227
+ id: options.sessionId,
1228
+ provider,
1229
+ }));
1230
+ const nowMs = Date.now();
1231
+ let root = options.root;
1232
+ if (root && root.aud && root.aud !== session.did) {
1233
+ root = undefined;
1234
+ }
1235
+ if (root && buildCapsKey(root.cap) !== buildCapsKey(caps)) {
1236
+ root = undefined;
1237
+ }
1238
+ if (root && root.exp && nowMs > root.exp) {
1239
+ root = undefined;
1240
+ }
1241
+ if (!root) {
1242
+ root = await getOrCreateUcanRoot({
1243
+ provider: provider || (await requireProvider()),
1244
+ session,
1245
+ capabilities: caps,
1246
+ expiresInMs: options.rootExpiresInMs,
1247
+ });
1248
+ }
1249
+ const invocationCaps = options.invocationCapabilities || caps;
1250
+ const token = await getCachedInvocationToken({
1251
+ issuer: session,
1252
+ audience: options.audience,
1253
+ capabilities: invocationCaps,
1254
+ proofs: [root],
1255
+ expiresInMs: options.invocationExpiresInMs,
1256
+ notBeforeMs: options.notBeforeMs,
1257
+ });
1258
+ const client = createWebDavClient({
1259
+ baseUrl: options.baseUrl,
1260
+ prefix: options.prefix,
1261
+ token,
1262
+ fetcher: options.fetcher,
1263
+ credentials: options.credentials,
1264
+ });
1265
+ const appDir = resolveAppDir(options);
1266
+ if (appDir && options.ensureAppDir !== false) {
1267
+ await client.ensureDirectory(appDir);
1268
+ }
1269
+ return {
1270
+ client,
1271
+ token,
1272
+ appDir,
1273
+ session,
1274
+ root,
1275
+ };
1276
+ }
1277
+ async function initDappSession(options) {
1278
+ if (!options.appAuth && !options.webdav) {
1279
+ throw new Error('No init options provided');
1280
+ }
1281
+ const provider = options.provider ||
1282
+ options.appAuth?.provider ||
1283
+ options.webdav?.provider ||
1284
+ (await requireProvider());
1285
+ const result = {
1286
+ provider,
1287
+ address: options.address,
1288
+ };
1289
+ if (options.appAuth) {
1290
+ const appLogin = await loginWithChallenge({
1291
+ ...options.appAuth,
1292
+ provider: options.appAuth.provider || provider,
1293
+ address: options.appAuth.address || options.address,
1294
+ });
1295
+ result.appLogin = appLogin;
1296
+ result.address = appLogin.address;
1297
+ }
1298
+ if (options.webdav) {
1299
+ const webdav = await initWebDavStorage({
1300
+ ...options.webdav,
1301
+ provider: options.webdav.provider || provider,
1302
+ });
1303
+ result.ucanSession = webdav.session;
1304
+ result.ucanRoot = webdav.root;
1305
+ result.webdavClient = webdav.client;
1306
+ result.webdavToken = webdav.token;
1307
+ result.webdavAppDir = webdav.appDir;
1308
+ }
1309
+ return result;
1310
+ }
1311
+
1017
1312
  exports.WebDavClient = WebDavClient;
1018
1313
  exports.authFetch = authFetch;
1019
1314
  exports.authUcanFetch = authUcanFetch;
@@ -1028,9 +1323,13 @@
1028
1323
  exports.getAccounts = getAccounts;
1029
1324
  exports.getBalance = getBalance;
1030
1325
  exports.getChainId = getChainId;
1326
+ exports.getOrCreateUcanRoot = getOrCreateUcanRoot;
1327
+ exports.getPreferredAccount = getPreferredAccount;
1031
1328
  exports.getProvider = getProvider;
1032
1329
  exports.getStoredUcanRoot = getStoredUcanRoot;
1033
1330
  exports.getUcanSession = getUcanSession;
1331
+ exports.initDappSession = initDappSession;
1332
+ exports.initWebDavStorage = initWebDavStorage;
1034
1333
  exports.isYeYingProvider = isYeYingProvider;
1035
1334
  exports.loginWithChallenge = loginWithChallenge;
1036
1335
  exports.logout = logout;
@@ -1042,6 +1341,7 @@
1042
1341
  exports.setAccessToken = setAccessToken;
1043
1342
  exports.signMessage = signMessage;
1044
1343
  exports.storeUcanRoot = storeUcanRoot;
1344
+ exports.watchAccounts = watchAccounts;
1045
1345
 
1046
1346
  }));
1047
1347
  //# sourceMappingURL=web3-bs.umd.js.map