@invisi-labs/sdk 1.1.3 → 1.1.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.
package/dist/index.js CHANGED
@@ -1,29 +1,34 @@
1
1
  import {
2
2
  DisclosureVerifier,
3
+ InvisiError,
3
4
  Operation,
4
5
  TransactionPreparation,
5
6
  WalletSession,
6
- authorizeOperation,
7
+ acceptFundingSignature,
8
+ acceptWalletSignature,
7
9
  copyGasBenchmarks,
10
+ decryptCandidates,
8
11
  defineGasBenchmarks,
9
12
  deserializeState,
10
13
  encodeOperation,
11
14
  estimateGasReference,
15
+ fundingSigningRequest,
12
16
  operationPreview,
13
17
  positiveAmount,
18
+ prepareWithErrors,
14
19
  proveOperation,
15
20
  publicProof,
21
+ readProtocolLogs,
16
22
  requiresAuthorization,
17
23
  sameStatement,
18
24
  serializeState,
19
- signOperationFunding,
20
25
  statement,
21
- walletBalance
22
- } from "./chunk-FSQYZO66.js";
26
+ walletBalance,
27
+ walletSigningRequest
28
+ } from "./chunk-BE52B32Z.js";
23
29
  import {
24
30
  EthersAddressResolver,
25
31
  EthersChainReader,
26
- EthersFundingSigner,
27
32
  EthersFundingVerifier
28
33
  } from "./chunk-4HMM7ZKY.js";
29
34
 
@@ -31,9 +36,7 @@ import {
31
36
  import { getAddress } from "ethers";
32
37
 
33
38
  // src/PreparedTransaction.ts
34
- import {
35
- MissingCapabilityError
36
- } from "@invisi-labs/core";
39
+ import { MissingCapabilityError } from "@invisi-labs/core";
37
40
  import { normalizeFunding } from "@invisi-labs/protocol";
38
41
  function check(ok, message) {
39
42
  if (!ok) throw new Error(message);
@@ -104,30 +107,77 @@ var PreparedTransaction = class _PreparedTransaction {
104
107
  this.#lifecycle.busy = false;
105
108
  }
106
109
  }
107
- async authorize(providerOrOptions = {}, suppliedOptions = {}) {
108
- const options = "authorize" in providerOrOptions ? suppliedOptions : providerOrOptions;
109
- const provider = "authorize" in providerOrOptions ? providerOrOptions : this.#runtime.authorization();
110
+ /** Public EIP-712 request; signing transport and key custody belong to the caller. */
111
+ async exportWalletAuthorization(options = {}) {
112
+ options.signal?.throwIfAborted();
113
+ this.current();
114
+ check(
115
+ requiresAuthorization(this.#saved.plan),
116
+ "operation does not require wallet authorization"
117
+ );
118
+ check(
119
+ ["prepared", "authorized"].includes(this.status),
120
+ "operation is not awaiting wallet authorization"
121
+ );
122
+ return walletSigningRequest(this.#saved.plan);
123
+ }
124
+ /** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
125
+ async withWalletSignature(signature, options = {}) {
126
+ options.signal?.throwIfAborted();
127
+ this.current();
110
128
  check(
111
129
  requiresAuthorization(this.#saved.plan),
112
130
  "operation does not require wallet authorization"
113
131
  );
114
- const selectedProvider = provider;
115
132
  check(
116
- provider.walletIdentity === this.#runtime.scope.walletIdentity,
117
- "wallet authorization capability mismatch"
133
+ ["prepared", "authorized"].includes(this.status),
134
+ "operation is not awaiting wallet authorization"
118
135
  );
136
+ const authorization = await acceptWalletSignature(this.#saved.plan, signature);
119
137
  return this.stage(
120
138
  "wallet authorization",
121
139
  ["prepared", "authorized"],
140
+ async () => ({ status: "authorized", authorization }),
141
+ options.signal
142
+ );
143
+ }
144
+ /** Export only after proving when the funding authorization binds an actual proof hash. */
145
+ async exportFundingAuthorization(options = {}) {
146
+ options.signal?.throwIfAborted();
147
+ this.current();
148
+ check(
149
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
150
+ "operation is not awaiting funding authorization or proof"
151
+ );
152
+ const request = await fundingSigningRequest(this.#saved, this.#runtime);
153
+ options.signal?.throwIfAborted();
154
+ this.current();
155
+ return request;
156
+ }
157
+ /** Verify an external signature against this handle's own frozen operation. */
158
+ async withFundingSignature(signature, options = {}) {
159
+ options.signal?.throwIfAborted();
160
+ this.current();
161
+ check(
162
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
163
+ "operation is not awaiting funding authorization or proof"
164
+ );
165
+ const verified = await acceptFundingSignature(this.#saved, signature, this.#runtime);
166
+ return this.stage(
167
+ "funding authorization",
168
+ this.kind === "shield" ? ["prepared"] : ["proved"],
122
169
  async (saved) => {
123
- const authorization = await authorizeOperation(saved.plan, selectedProvider);
124
- return { status: "authorized", authorization };
170
+ saved.plan.funding.signature = verified;
171
+ if (saved.plan.statement?.target !== "noir-recursive") {
172
+ saved.call = await this.encode(saved);
173
+ }
174
+ return { status: "ready" };
125
175
  },
126
176
  options.signal
127
177
  );
128
178
  }
129
179
  async prove(backendOrOptions = {}, suppliedOptions = {}) {
130
- const options = "prove" in backendOrOptions ? suppliedOptions : backendOrOptions;
180
+ const options = { ..."prove" in backendOrOptions ? suppliedOptions : backendOrOptions };
131
181
  const plan = this.#saved.plan;
132
182
  check(plan.kind !== "shield", "shield requires no proof");
133
183
  if (requiresAuthorization(plan)) {
@@ -166,37 +216,6 @@ var PreparedTransaction = class _PreparedTransaction {
166
216
  encode(saved) {
167
217
  return encodeOperation(saved);
168
218
  }
169
- async authorizeFunding(signerOrOptions = {}, suppliedOptions = {}) {
170
- const options = "signTypedData" in signerOrOptions ? suppliedOptions : signerOrOptions;
171
- const suppliedSigner = "signTypedData" in signerOrOptions ? signerOrOptions : void 0;
172
- const p = this.#saved.plan;
173
- check(p.fundingMode === "signature", "operation does not require a funding signature");
174
- check(p.kind === "shield" || this.#saved.proof, "proof must precede funding authorization");
175
- const signer = suppliedSigner ?? this.#runtime.options.fundingSigner;
176
- if (!signer) {
177
- throw new MissingCapabilityError("EVM funding signer");
178
- }
179
- check(
180
- signer.address.toLowerCase() === p.funding.payer.toLowerCase(),
181
- "funding signer is not the payer"
182
- );
183
- return this.stage(
184
- "funding authorization",
185
- p.kind === "shield" ? ["prepared"] : ["proved"],
186
- async (saved) => {
187
- saved.plan.funding.signature = await signOperationFunding(
188
- saved,
189
- signer,
190
- this.#runtime.options.fundingVerifier
191
- );
192
- if (saved.plan.statement?.target !== "noir-recursive") {
193
- saved.call = await this.encode(saved);
194
- }
195
- return { status: "ready" };
196
- },
197
- options.signal
198
- );
199
- }
200
219
  /** Shield caller funding needs no wallet signing or proof; this step only encodes its frozen request. */
201
220
  async ready() {
202
221
  check(
@@ -285,14 +304,21 @@ function explicitPreparation(options, data, tokenAddress) {
285
304
  namespace: "explicit",
286
305
  walletIdentity: options.wallet.walletIdentity
287
306
  };
288
- const { store: _store, coinSelector: _selector, ...capabilities } = options;
307
+ const {
308
+ store: _store,
309
+ coinSelector: _selector,
310
+ authorization: _authorization,
311
+ viewing: _viewing,
312
+ signer: _signer,
313
+ fundingSigner: _fundingSigner,
314
+ ...capabilities
315
+ } = options;
289
316
  if (inputs.some((input) => input.note.tokenAddress !== BigInt(getAddress(tokenAddress))))
290
317
  throw new Error("explicit note token mismatch");
291
318
  const runtime = {
292
319
  options: { ...capabilities, protocol: options.protocol.forToken(tokenAddress) },
293
320
  reader: options.chainReader,
294
- scope,
295
- authorization: () => options.authorization ?? options.wallet.exportAuthorization()
321
+ scope
296
322
  };
297
323
  return new TransactionPreparation({
298
324
  ...runtime,
@@ -305,10 +331,39 @@ function explicitPreparation(options, data, tokenAddress) {
305
331
 
306
332
  // src/Invisi.ts
307
333
  import { ProtocolRpcClient } from "@invisi-labs/rpc";
308
- import { MissingCapabilityError as MissingCapabilityError2 } from "@invisi-labs/core";
334
+ import { MissingCapabilityError as MissingCapabilityError2, PublicWallet, ReceivingAddress } from "@invisi-labs/core";
335
+
336
+ // src/AddressRegistry.ts
337
+ import { AddressRegistry as Registry } from "@invisi-labs/protocol";
338
+ var AddressRegistry = class extends Registry {
339
+ prepareRegistration(options) {
340
+ return prepareWithErrors("prepareRegistration", async () => {
341
+ if ("provider" in options) {
342
+ const { provider, ...preparation } = options;
343
+ return super.prepareRegistration({
344
+ ...preparation,
345
+ reader: new EthersChainReader(provider)
346
+ });
347
+ }
348
+ return super.prepareRegistration(options);
349
+ });
350
+ }
351
+ };
352
+
353
+ // src/Invisi.ts
309
354
  var Invisi = class {
310
355
  #options;
311
356
  constructor(options = {}) {
357
+ if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
358
+ throw new Error(
359
+ "SDK envelope versions are fixed; encode application payload bytes externally"
360
+ );
361
+ }
362
+ if ("signer" in options || "fundingSigner" in options) {
363
+ throw new Error(
364
+ "Invisi does not accept signer defaults; export a signing request and attach the external signature"
365
+ );
366
+ }
312
367
  this.#options = {
313
368
  ...options,
314
369
  ...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
@@ -319,7 +374,15 @@ var Invisi = class {
319
374
  return new WalletSession(await this.resolve(options));
320
375
  }
321
376
  async resolve(options) {
322
- const allowed = ["wallet", "protocol", "namespace", "viewing", "authorization"];
377
+ const allowed = [
378
+ "wallet",
379
+ "protocol",
380
+ "namespace",
381
+ "viewing",
382
+ "authorization",
383
+ "signer",
384
+ "fundingSigner"
385
+ ];
323
386
  for (const key of Object.keys(options)) {
324
387
  if (!allowed.includes(key))
325
388
  throw new Error(
@@ -328,7 +391,10 @@ var Invisi = class {
328
391
  }
329
392
  const selected = { ...options };
330
393
  const combined = { ...this.#options, ...selected };
331
- const { provider, signer, ...runtime } = combined;
394
+ const { provider, addressLookup = "protocol-rpc", ...runtime } = combined;
395
+ if (addressLookup !== void 0 && addressLookup !== "chain" && addressLookup !== "protocol-rpc") {
396
+ throw new Error("Invalid recipient address lookup source");
397
+ }
332
398
  const reader = combined.chainReader ?? (provider ? new EthersChainReader(provider) : void 0);
333
399
  if (!reader) throw new MissingCapabilityError2("chain reader");
334
400
  if (provider && (await provider.getNetwork()).chainId !== selected.protocol.context.chainId)
@@ -337,88 +403,138 @@ var Invisi = class {
337
403
  if (runtime.protocolRpc instanceof ProtocolRpcClient) {
338
404
  runtime.protocolRpc = runtime.protocolRpc.withReader(reader);
339
405
  }
340
- if (signer) {
341
- if (!provider) throw new MissingCapabilityError2("provider for signer");
342
- if (!signer.provider) throw new Error("signer must be connected to a provider");
343
- if ((await signer.provider.getNetwork()).chainId !== selected.protocol.context.chainId)
344
- throw new Error("signer-provider deployment chain mismatch");
345
- }
346
406
  const registry = selected.protocol.deployment.registry;
347
- const fundingSigner = combined.fundingSigner ?? (signer ? await EthersFundingSigner.create(signer, selected.protocol.context.chainId) : void 0);
348
407
  const fundingVerifier = combined.fundingVerifier ?? (provider ? new EthersFundingVerifier(provider, selected.protocol.context.chainId) : void 0);
349
- const addressResolver = combined.addressResolver ?? (provider && registry ? new EthersAddressResolver(
350
- provider,
351
- {
352
- chainId: selected.protocol.context.chainId,
353
- registryAddress: registry.address
354
- },
355
- registry.runtimeCodeHash
356
- ).lookup : void 0);
408
+ let addressResolver = combined.addressResolver;
409
+ if (!addressResolver && registry) {
410
+ if (addressLookup === "protocol-rpc") {
411
+ const rpc = runtime.protocolRpc;
412
+ addressResolver = async (identity) => {
413
+ if (!(rpc instanceof ProtocolRpcClient)) {
414
+ throw new MissingCapabilityError2("Protocol RPC recipient discovery");
415
+ }
416
+ const discovered = await rpc.getDeployment(selected.protocol.context.chainId);
417
+ const expected = selected.protocol.deployment;
418
+ if (discovered.protocol.address !== expected.protocol.address || discovered.protocol.runtimeCodeHash !== expected.protocol.runtimeCodeHash || discovered.protocol.fromBlock !== expected.protocol.fromBlock || discovered.registry?.address !== registry.address || discovered.registry.runtimeCodeHash !== registry.runtimeCodeHash || discovered.registry.fromBlock !== registry.fromBlock)
419
+ throw new Error("Recipient registry deployment pins mismatch");
420
+ const registration = await rpc.getReceivingAddress(identity);
421
+ return registration ? ReceivingAddress.parse(registration.receivingAddress).getReceivingKeys() : null;
422
+ };
423
+ } else if (provider) {
424
+ addressResolver = new EthersAddressResolver(
425
+ provider,
426
+ {
427
+ chainId: selected.protocol.context.chainId,
428
+ registryAddress: registry.address
429
+ },
430
+ registry.runtimeCodeHash
431
+ ).lookup;
432
+ }
433
+ }
357
434
  return {
358
435
  ...runtime,
359
436
  chainReader: reader,
360
- ...signer ? { signer } : {},
361
- ...fundingSigner ? { fundingSigner } : {},
362
437
  ...fundingVerifier ? { fundingVerifier } : {},
363
438
  ...addressResolver ? { addressResolver } : {},
364
439
  protocol: selected.protocol
365
440
  };
366
441
  }
442
+ /** Discover the selected chain's registry and prepare an independent signing request. */
443
+ async prepareRegistration(options) {
444
+ return prepareWithErrors("prepareRegistration", async () => {
445
+ for (const key of Object.keys(options)) {
446
+ if (!["wallet", "update", "deadline"].includes(key)) {
447
+ throw new Error(`Unknown registration option: ${key}`);
448
+ }
449
+ }
450
+ this.assertPublicIdentity(options);
451
+ const selected = { ...options };
452
+ const { provider, protocolRpc } = this.#options;
453
+ if (!provider) throw new MissingCapabilityError2("ethers provider");
454
+ if (!(protocolRpc instanceof ProtocolRpcClient)) {
455
+ throw new MissingCapabilityError2("Protocol RPC deployment discovery");
456
+ }
457
+ const chainId = (await provider.getNetwork()).chainId;
458
+ const deployment = await protocolRpc.getDeployment(chainId);
459
+ if (!deployment.registry) {
460
+ throw new MissingCapabilityError2("receiving-key registry deployment");
461
+ }
462
+ const registry = new AddressRegistry(
463
+ { chainId, registryAddress: deployment.registry.address },
464
+ { expectedRuntimeCodeHash: deployment.registry.runtimeCodeHash }
465
+ );
466
+ return registry.prepareRegistration({ ...selected, provider });
467
+ });
468
+ }
469
+ assertPublicIdentity(options) {
470
+ const { wallet } = options;
471
+ if (!(wallet instanceof PublicWallet) || ["getViewer", "getDetector", "exportAuthorization"].some((key) => key in wallet)) {
472
+ throw new Error(
473
+ "Independent preparation requires a PublicWallet without private capabilities"
474
+ );
475
+ }
476
+ for (const key of ["authorization", "signer", "fundingSigner", "viewing"]) {
477
+ if (key in options)
478
+ throw new Error(`Independent preparation does not accept capability: ${key}`);
479
+ }
480
+ }
481
+ resolveExplicit(options) {
482
+ this.assertPublicIdentity(options);
483
+ return this.resolve({ wallet: options.wallet, protocol: options.protocol });
484
+ }
367
485
  async prepareTransfer(options) {
368
- const { wallet, protocol, ...input } = options;
369
- const request = structuredClone(input);
370
- const runtime = await this.resolve({ wallet, protocol });
371
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
372
- return preparation.prepareTransfer(request);
373
- }
374
- async prepareWithdraw(options) {
375
- const { wallet, protocol, ...input } = options;
376
- const request = structuredClone(input);
377
- const runtime = await this.resolve({ wallet, protocol });
378
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
379
- return preparation.prepareWithdraw(request);
380
- }
381
- async prepareSweep(options) {
382
- const { wallet, protocol, ...input } = options;
383
- const request = structuredClone(input);
384
- const runtime = await this.resolve({ wallet, protocol });
385
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
386
- return preparation.prepareSweep(request);
486
+ return prepareWithErrors("prepareTransfer", async () => {
487
+ const { wallet, protocol, ...input } = options;
488
+ const request = structuredClone(input);
489
+ const runtime = await this.resolveExplicit(options);
490
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
491
+ return preparation.prepareTransfer(request);
492
+ });
387
493
  }
388
494
  async prepareShield(options) {
389
- const { wallet, protocol, ...input } = options;
390
- const request = structuredClone(input);
391
- const runtime = await this.resolve({ wallet, protocol });
392
- const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
393
- return preparation.prepareShield(request);
495
+ return prepareWithErrors("prepareShield", async () => {
496
+ const { wallet, protocol, ...input } = options;
497
+ const request = structuredClone(input);
498
+ const runtime = await this.resolveExplicit(options);
499
+ const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
500
+ return preparation.prepareShield(request);
501
+ });
394
502
  }
395
503
  async prepareCreateCredit(options) {
396
- const { wallet, protocol, ...input } = options;
397
- const request = structuredClone(input);
398
- const runtime = await this.resolve({ wallet, protocol });
399
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
400
- return preparation.prepareCreateCredit(request);
504
+ return prepareWithErrors("prepareCreateCredit", async () => {
505
+ const { wallet, protocol, ...input } = options;
506
+ const request = structuredClone(input);
507
+ const runtime = await this.resolveExplicit(options);
508
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
509
+ return preparation.prepareCreateCredit(request);
510
+ });
401
511
  }
402
512
  async prepareDrawCredit(options) {
403
- const { wallet, protocol, ...input } = options;
404
- const request = structuredClone(input);
405
- const runtime = await this.resolve({ wallet, protocol });
406
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
407
- return preparation.prepareDrawCredit(request);
513
+ return prepareWithErrors("prepareDrawCredit", async () => {
514
+ const { wallet, protocol, ...input } = options;
515
+ const request = structuredClone(input);
516
+ const runtime = await this.resolveExplicit(options);
517
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
518
+ return preparation.prepareDrawCredit(request);
519
+ });
408
520
  }
409
521
  async prepareTopUpCredit(options) {
410
- const { wallet, protocol, ...input } = options;
411
- const request = structuredClone(input);
412
- const runtime = await this.resolve({ wallet, protocol });
413
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
414
- return preparation.prepareTopUpCredit(request);
522
+ return prepareWithErrors("prepareTopUpCredit", async () => {
523
+ const { wallet, protocol, ...input } = options;
524
+ const request = structuredClone(input);
525
+ const runtime = await this.resolveExplicit(options);
526
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
527
+ return preparation.prepareTopUpCredit(request);
528
+ });
415
529
  }
416
530
  async prepareCancelCredit(options) {
417
- const { wallet, protocol, ...input } = options;
418
- const request = structuredClone(input);
419
- const runtime = await this.resolve({ wallet, protocol });
420
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
421
- return preparation.prepareCancelCredit(request);
531
+ return prepareWithErrors("prepareCancelCredit", async () => {
532
+ const { wallet, protocol, ...input } = options;
533
+ const request = structuredClone(input);
534
+ const runtime = await this.resolveExplicit(options);
535
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
536
+ return preparation.prepareCancelCredit(request);
537
+ });
422
538
  }
423
539
  toJSON() {
424
540
  return { invisi: true };
@@ -430,7 +546,11 @@ var Invisi = class {
430
546
 
431
547
  // src/index.ts
432
548
  import {
549
+ fundsV1,
550
+ creditV1,
551
+ decodeDeliveryHeader,
433
552
  Wallet,
553
+ PublicWallet as PublicWallet2,
434
554
  ViewingCapability,
435
555
  Detector,
436
556
  NullifierCapability,
@@ -439,7 +559,9 @@ import {
439
559
  Disclosure,
440
560
  encodeMemo,
441
561
  decodeMemoText,
442
- MAX_MEMO_BYTES
562
+ MAX_MEMO_BYTES,
563
+ computeCreditNullifier,
564
+ creditPeriods
443
565
  } from "@invisi-labs/core";
444
566
  import { Protocol, RegistrationOperation } from "@invisi-labs/protocol";
445
567
  import { ProtocolRpcError } from "@invisi-labs/rpc";
@@ -452,21 +574,6 @@ var ProtocolRpcClient2 = class extends RpcClient {
452
574
  }
453
575
  };
454
576
 
455
- // src/AddressRegistry.ts
456
- import { AddressRegistry as Registry } from "@invisi-labs/protocol";
457
- var AddressRegistry = class extends Registry {
458
- prepareRegistration(options) {
459
- if ("provider" in options) {
460
- const { provider, ...preparation } = options;
461
- return super.prepareRegistration({
462
- ...preparation,
463
- reader: new EthersChainReader(provider)
464
- });
465
- }
466
- return super.prepareRegistration(options);
467
- }
468
- };
469
-
470
577
  // src/integrations/encrypted-codec.ts
471
578
  var EncryptedWalletCodec = class _EncryptedWalletCodec {
472
579
  #key;
@@ -557,6 +664,7 @@ export {
557
664
  DisclosureVerifier,
558
665
  EncryptedWalletCodec,
559
666
  Invisi,
667
+ InvisiError,
560
668
  MAX_MEMO_BYTES,
561
669
  MissingCapabilityError3 as MissingCapabilityError,
562
670
  NullifierCapability,
@@ -565,14 +673,22 @@ export {
565
673
  Protocol,
566
674
  ProtocolRpcClient2 as ProtocolRpcClient,
567
675
  ProtocolRpcError,
676
+ PublicWallet2 as PublicWallet,
568
677
  RegistrationOperation,
569
678
  ViewingCapability,
570
679
  Wallet,
571
680
  WalletAuthorization,
572
681
  WalletSession,
682
+ computeCreditNullifier,
683
+ creditPeriods,
684
+ creditV1,
685
+ decodeDeliveryHeader,
573
686
  decodeMemoText,
687
+ decryptCandidates,
574
688
  defineGasBenchmarks,
575
689
  encodeMemo,
690
+ fundsV1,
691
+ readProtocolLogs,
576
692
  walletBalance
577
693
  };
578
694
  //# sourceMappingURL=index.js.map