@substrate/connect
Advanced tools
Comparing version 0.8.2 to 0.8.3
@@ -7,2 +7,8 @@ # Changelog | ||
## 0.8.3 - 2023-12-07 | ||
### Changed | ||
- Use `@polkadot-api/light-client-extension-helpers/web-page` ([#1603](https://github.com/paritytech/substrate-connect/pull/1603)) | ||
## 0.8.2 - 2023-12-06 | ||
@@ -9,0 +15,0 @@ |
@@ -331,166 +331,38 @@ var __defProp = Object.defineProperty; | ||
// src/connector/extension.ts | ||
var listeners = /* @__PURE__ */ new Map(); | ||
if (typeof window === "object") { | ||
window.addEventListener( | ||
"message", | ||
({ data }) => { | ||
var _a; | ||
if ((data == null ? void 0 : data.origin) !== "substrate-connect-extension") | ||
return; | ||
(_a = listeners.get(data.chainId)) == null ? void 0 : _a(data); | ||
} | ||
); | ||
} | ||
function getRandomChainId() { | ||
const arr = new BigUint64Array(2); | ||
crypto.getRandomValues(arr); | ||
const result = arr[1] << BigInt(64) | arr[0]; | ||
return result.toString(36); | ||
} | ||
import { DOM_ELEMENT_ID } from "@substrate/connect-extension-protocol"; | ||
var wellKnownChainGenesisHashes = { | ||
polkadot: "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3", | ||
ksmcc3: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe", | ||
westend2: "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e", | ||
rococo_v2_2: "0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e" | ||
}; | ||
var lightClientProviderPromise; | ||
var createScClient2 = () => { | ||
const internalAddChain = (isWellKnown, chainSpecOrWellKnownName, jsonRpcCallback, relayChainId) => __async(void 0, null, function* () { | ||
let resolve; | ||
const initFinished = new Promise((res) => { | ||
resolve = () => res(null); | ||
}); | ||
const chainState = { | ||
id: getRandomChainId(), | ||
state: { | ||
state: "pending", | ||
waitFinished: resolve | ||
} | ||
}; | ||
if (listeners.has(chainState.id)) | ||
throw new Error( | ||
"Unexpectedly randomly generated the same chain ID twice despite 64bits of entropy" | ||
if (!lightClientProviderPromise) | ||
lightClientProviderPromise = import("@polkadot-api/light-client-extension-helpers/web-page").then( | ||
({ getLightClientProvider }) => getLightClientProvider(DOM_ELEMENT_ID) | ||
); | ||
const internalAddChain = (isWellKnown, chainSpecOrWellKnownName, jsonRpcCallback, relayChainGenesisHash) => __async(void 0, null, function* () { | ||
const lightClientProvider = yield lightClientProviderPromise; | ||
let chain; | ||
if (isWellKnown) { | ||
const foundChain = Object.values(lightClientProvider.getChains()).find( | ||
({ genesisHash }) => genesisHash === wellKnownChainGenesisHashes[chainSpecOrWellKnownName] | ||
); | ||
listeners.set(chainState.id, (msg) => { | ||
switch (chainState.state.state) { | ||
case "pending": { | ||
const waitFinished = chainState.state.waitFinished; | ||
switch (msg.type) { | ||
case "chain-ready": { | ||
chainState.state = { | ||
state: "ok" | ||
}; | ||
break; | ||
} | ||
case "error": { | ||
chainState.state = { | ||
state: "dead", | ||
error: new CrashError( | ||
"Error while creating the chain: " + msg.errorMessage | ||
) | ||
}; | ||
break; | ||
} | ||
default: { | ||
console.warn( | ||
"Unexpected message of type `msg.type` received from substrate-connect extension" | ||
); | ||
} | ||
} | ||
waitFinished(); | ||
break; | ||
} | ||
case "ok": { | ||
switch (msg.type) { | ||
case "error": { | ||
chainState.state = { | ||
state: "dead", | ||
error: new CrashError( | ||
"Extension has killed the chain: " + msg.errorMessage | ||
) | ||
}; | ||
break; | ||
} | ||
case "rpc": { | ||
if (jsonRpcCallback) { | ||
jsonRpcCallback(msg.jsonRpcMessage); | ||
} else { | ||
console.warn( | ||
"Unexpected message of type `msg.type` received from substrate-connect extension" | ||
); | ||
} | ||
break; | ||
} | ||
default: { | ||
console.warn( | ||
"Unexpected message of type `msg.type` received from substrate-connect extension" | ||
); | ||
} | ||
} | ||
break; | ||
} | ||
case "dead": { | ||
break; | ||
} | ||
} | ||
}); | ||
if (isWellKnown) { | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "add-well-known-chain", | ||
chainName: chainSpecOrWellKnownName | ||
}); | ||
if (!foundChain) | ||
throw new Error("Unknown well-known chain"); | ||
chain = foundChain; | ||
} else { | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "add-chain", | ||
chainSpec: chainSpecOrWellKnownName, | ||
potentialRelayChainIds: relayChainId ? [relayChainId] : [] | ||
}); | ||
chain = yield lightClientProvider.getChain( | ||
chainSpecOrWellKnownName, | ||
relayChainGenesisHash | ||
); | ||
} | ||
yield initFinished; | ||
if (isWellKnown && chainState.state.state === "dead") { | ||
let resolve2; | ||
const initFinished2 = new Promise((res) => { | ||
resolve2 = () => res(null); | ||
}); | ||
chainState.state = { | ||
state: "pending", | ||
waitFinished: resolve2 | ||
}; | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "add-chain", | ||
chainSpec: yield getSpec(chainSpecOrWellKnownName), | ||
potentialRelayChainIds: [] | ||
}); | ||
yield initFinished2; | ||
} | ||
if (chainState.state.state === "dead") { | ||
throw chainState.state.error; | ||
} | ||
const chain = { | ||
sendJsonRpc: (jsonRpcMessage) => { | ||
if (chainState.state.state === "dead") { | ||
throw chainState.state.error; | ||
} | ||
if (!jsonRpcCallback) | ||
throw new JsonRpcDisabledError(); | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "rpc", | ||
jsonRpcMessage | ||
}); | ||
const jsonRpcProvider = chain.connect(jsonRpcCallback); | ||
return { | ||
sendJsonRpc(rpc) { | ||
jsonRpcProvider.send(rpc); | ||
}, | ||
remove: () => { | ||
if (chainState.state.state === "dead") { | ||
throw chainState.state.error; | ||
} | ||
chainState.state = { | ||
state: "dead", | ||
error: new AlreadyDestroyedError() | ||
}; | ||
listeners.delete(chainState.id); | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "remove-chain" | ||
}); | ||
remove() { | ||
jsonRpcProvider.disconnect(); | ||
}, | ||
@@ -502,7 +374,6 @@ addChain: function(chainSpec, jsonRpcCallback2) { | ||
jsonRpcCallback2, | ||
chainState.id | ||
chain.genesisHash | ||
); | ||
} | ||
}; | ||
return chain; | ||
}); | ||
@@ -514,9 +385,7 @@ return { | ||
}; | ||
function postToExtension(msg) { | ||
window.postMessage(msg, "*"); | ||
} | ||
// src/connector/index.ts | ||
import { DOM_ELEMENT_ID } from "@substrate/connect-extension-protocol"; | ||
var isExtensionPresent = typeof document === "object" && typeof document.getElementById === "function" && !!document.getElementById(DOM_ELEMENT_ID); | ||
import { DOM_ELEMENT_ID as DOM_ELEMENT_ID2 } from "@substrate/connect-extension-protocol"; | ||
var _a; | ||
var isExtensionPresent = typeof document === "object" && typeof document.getElementById === "function" && !!document.getElementById(DOM_ELEMENT_ID2) && ((_a = document.getElementById(DOM_ELEMENT_ID2)) == null ? void 0 : _a.getAttribute("channelid")) === DOM_ELEMENT_ID2; | ||
function createScClient3(config) { | ||
@@ -523,0 +392,0 @@ const forceEmbedded = config == null ? void 0 : config.forceEmbeddedNode; |
{ | ||
"name": "@substrate/connect", | ||
"version": "0.8.2", | ||
"version": "0.8.3", | ||
"description": "Substrate-connect to Smoldot clients. Using either substrate extension with predefined clients or an internal smoldot client based on chainSpecs provided.", | ||
@@ -45,4 +45,5 @@ "author": "Parity Team <admin@parity.io>", | ||
"dependencies": { | ||
"@substrate/connect-extension-protocol": "^1.0.1", | ||
"@substrate/connect-known-chains": "^1.0.2", | ||
"@polkadot-api/light-client-extension-helpers": "next", | ||
"@substrate/connect-extension-protocol": "^2.0.0", | ||
"@substrate/connect-known-chains": "^1.0.3", | ||
"smoldot": "2.0.13" | ||
@@ -49,0 +50,0 @@ }, |
@@ -0,34 +1,20 @@ | ||
import { DOM_ELEMENT_ID } from "@substrate/connect-extension-protocol" | ||
import { type Chain, type JsonRpcCallback, type ScClient } from "./types.js" | ||
import type { | ||
ToApplication, | ||
ToExtension, | ||
} from "@substrate/connect-extension-protocol" | ||
import { | ||
AlreadyDestroyedError, | ||
CrashError, | ||
JsonRpcDisabledError, | ||
type Chain, | ||
type JsonRpcCallback, | ||
type ScClient, | ||
} from "./types.js" | ||
RawChain, | ||
LightClientProvider, | ||
} from "@polkadot-api/light-client-extension-helpers/web-page" | ||
import { WellKnownChain } from "../WellKnownChain.js" | ||
import { getSpec } from "./getSpec.js" | ||
const listeners = new Map<string, (msg: ToApplication) => void>() | ||
if (typeof window === "object") { | ||
window.addEventListener( | ||
"message", | ||
({ data }: MessageEvent<ToApplication>) => { | ||
if (data?.origin !== "substrate-connect-extension") return | ||
listeners.get(data.chainId)?.(data) | ||
}, | ||
) | ||
const wellKnownChainGenesisHashes: Record<string, string> = { | ||
polkadot: | ||
"0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3", | ||
ksmcc3: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe", | ||
westend2: | ||
"0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e", | ||
rococo_v2_2: | ||
"0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e", | ||
} | ||
function getRandomChainId(): string { | ||
const arr = new BigUint64Array(2) | ||
// It can only be used from the browser, so this is fine. | ||
crypto.getRandomValues(arr) | ||
const result = (arr[1]! << BigInt(64)) | arr[0]! | ||
return result.toString(36) | ||
} | ||
let lightClientProviderPromise: Promise<LightClientProvider> | ||
@@ -45,2 +31,8 @@ /** | ||
export const createScClient = (): ScClient => { | ||
if (!lightClientProviderPromise) | ||
lightClientProviderPromise = import( | ||
"@polkadot-api/light-client-extension-helpers/web-page" | ||
).then(({ getLightClientProvider }) => | ||
getLightClientProvider(DOM_ELEMENT_ID), | ||
) | ||
const internalAddChain = async ( | ||
@@ -50,195 +42,29 @@ isWellKnown: boolean, | ||
jsonRpcCallback?: JsonRpcCallback, | ||
relayChainId?: string, | ||
relayChainGenesisHash?: string, | ||
): Promise<Chain> => { | ||
type ChainState = | ||
| { | ||
state: "pending" | ||
waitFinished: () => void | ||
} | ||
| { | ||
state: "ok" | ||
} | ||
| { state: "dead"; error: AlreadyDestroyedError | CrashError } | ||
const lightClientProvider = await lightClientProviderPromise | ||
let resolve: undefined | (() => void) | ||
const initFinished = new Promise((res) => { | ||
resolve = () => res(null) | ||
}) | ||
const chainState: { id: string; state: ChainState } = { | ||
id: getRandomChainId(), | ||
state: { | ||
state: "pending", | ||
waitFinished: resolve!, | ||
}, | ||
} | ||
if (listeners.has(chainState.id)) | ||
throw new Error( | ||
"Unexpectedly randomly generated the same chain ID twice despite 64bits of entropy", | ||
let chain: RawChain | ||
if (isWellKnown) { | ||
const foundChain = Object.values(lightClientProvider.getChains()).find( | ||
({ genesisHash }) => | ||
genesisHash === wellKnownChainGenesisHashes[chainSpecOrWellKnownName], | ||
) | ||
// Setup the listener for this chain. | ||
// This listener should never be removed until we are no longer interested in this chain. | ||
// Removing then re-adding the listener could cause messages to be missed. | ||
listeners.set(chainState.id, (msg) => { | ||
switch (chainState.state.state) { | ||
case "pending": { | ||
const waitFinished = chainState.state.waitFinished | ||
switch (msg.type) { | ||
case "chain-ready": { | ||
chainState.state = { | ||
state: "ok", | ||
} | ||
break | ||
} | ||
case "error": { | ||
chainState.state = { | ||
state: "dead", | ||
error: new CrashError( | ||
"Error while creating the chain: " + msg.errorMessage, | ||
), | ||
} | ||
break | ||
} | ||
default: { | ||
// Unexpected message. We ignore it. | ||
// While it could be tempting to switch the chain to `dead`, the extension might | ||
// think that the chain is still alive, and the state mismatch could have | ||
// unpredictable and confusing consequences. | ||
console.warn( | ||
"Unexpected message of type `msg.type` received from substrate-connect extension", | ||
) | ||
} | ||
} | ||
waitFinished() | ||
break | ||
} | ||
case "ok": { | ||
switch (msg.type) { | ||
case "error": { | ||
chainState.state = { | ||
state: "dead", | ||
error: new CrashError( | ||
"Extension has killed the chain: " + msg.errorMessage, | ||
), | ||
} | ||
break | ||
} | ||
case "rpc": { | ||
if (jsonRpcCallback) { | ||
jsonRpcCallback(msg.jsonRpcMessage) | ||
} else { | ||
console.warn( | ||
"Unexpected message of type `msg.type` received from substrate-connect extension", | ||
) | ||
} | ||
break | ||
} | ||
default: { | ||
// Unexpected message. We ignore it. | ||
// While it could be tempting to switch the chain to `dead`, the extension might | ||
// think that the chain is still alive, and the state mismatch could have | ||
// unpredictable and confusing consequences. | ||
console.warn( | ||
"Unexpected message of type `msg.type` received from substrate-connect extension", | ||
) | ||
} | ||
} | ||
break | ||
} | ||
case "dead": { | ||
// We don't expect any message anymore. | ||
break | ||
} | ||
} | ||
}) | ||
// Now that everything is ready to receive messages back from the extension, send the | ||
// add-chain message. | ||
if (isWellKnown) { | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "add-well-known-chain", | ||
chainName: chainSpecOrWellKnownName, | ||
}) | ||
if (!foundChain) throw new Error("Unknown well-known chain") | ||
chain = foundChain | ||
} else { | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "add-chain", | ||
chainSpec: chainSpecOrWellKnownName, | ||
potentialRelayChainIds: relayChainId ? [relayChainId] : [], | ||
}) | ||
chain = await lightClientProvider.getChain( | ||
chainSpecOrWellKnownName, | ||
relayChainGenesisHash, | ||
) | ||
} | ||
// Wait for the extension to send back either a confirmation or an error. | ||
// Note that `initFinished` becomes ready when `chainState` has been modified. The outcome | ||
// can be known by looking into `chainState`. | ||
await initFinished | ||
const jsonRpcProvider = chain.connect(jsonRpcCallback!) | ||
// In the situation where we tried to create a well-known chain, the extension isn't supposed | ||
// to ever return an error. There is however one situation where errors can happen: if the | ||
// extension doesn't recognize the desired well-known chain because it uses a different list | ||
// of well-known chains than this code. To handle this, we download the chain spec of the | ||
// desired well-known chain and try again but this time as a non-well-known chain. | ||
if (isWellKnown && chainState.state.state === "dead") { | ||
// Note that we keep the same id for the chain for convenience. | ||
let resolve: undefined | (() => void) | ||
const initFinished = new Promise((res) => { | ||
resolve = () => res(null) | ||
}) | ||
chainState.state = { | ||
state: "pending", | ||
waitFinished: resolve!, | ||
} | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "add-chain", | ||
chainSpec: await getSpec(chainSpecOrWellKnownName), | ||
potentialRelayChainIds: [], | ||
}) | ||
await initFinished | ||
} | ||
// Now check the `chainState` to know if things have succeeded. | ||
if (chainState.state.state === "dead") { | ||
throw chainState.state.error | ||
} | ||
// Everything is successful. | ||
const chain: Chain = { | ||
sendJsonRpc: (jsonRpcMessage) => { | ||
if (chainState.state.state === "dead") { | ||
throw chainState.state.error | ||
} | ||
if (!jsonRpcCallback) throw new JsonRpcDisabledError() | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "rpc", | ||
jsonRpcMessage, | ||
}) | ||
return { | ||
sendJsonRpc(rpc: string): void { | ||
jsonRpcProvider.send(rpc) | ||
}, | ||
remove: () => { | ||
if (chainState.state.state === "dead") { | ||
throw chainState.state.error | ||
} | ||
chainState.state = { | ||
state: "dead", | ||
error: new AlreadyDestroyedError(), | ||
} | ||
listeners.delete(chainState.id) | ||
postToExtension({ | ||
origin: "substrate-connect-client", | ||
chainId: chainState.id, | ||
type: "remove-chain", | ||
}) | ||
remove() { | ||
jsonRpcProvider.disconnect() | ||
}, | ||
@@ -253,8 +79,6 @@ addChain: function ( | ||
jsonRpcCallback, | ||
chainState.id, | ||
chain.genesisHash, | ||
) | ||
}, | ||
} | ||
return chain | ||
} | ||
@@ -271,7 +95,1 @@ | ||
} | ||
// Sends a message to the extension. This function primarly exists in order to provide strong | ||
// typing for the message. | ||
function postToExtension(msg: ToExtension) { | ||
window.postMessage(msg, "*") | ||
} |
@@ -30,3 +30,5 @@ import { | ||
typeof document.getElementById === "function" && | ||
!!document.getElementById(DOM_ELEMENT_ID) | ||
!!document.getElementById(DOM_ELEMENT_ID) && | ||
document.getElementById(DOM_ELEMENT_ID)?.getAttribute("channelid") === | ||
DOM_ELEMENT_ID | ||
@@ -33,0 +35,0 @@ /** |
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import { beforeEach, beforeAll, it, describe, expect, vi } from "vitest" | ||
import type { AddChainOptions, ClientOptions } from "smoldot" | ||
import { WellKnownChain } from "../WellKnownChain" | ||
import { WellKnownChain } from "../WellKnownChain.js" | ||
@@ -125,3 +125,3 @@ class SdAlreadyDestroyedError extends Error { | ||
it("does not eagerly instantiate the client", () => { | ||
import("./smoldot-light").then((smoldot) => { | ||
import("./smoldot-light.js").then((smoldot) => { | ||
smoldot.createScClient() | ||
@@ -136,3 +136,3 @@ mockedSmoldotLight = | ||
it("terminates the internal client when all the chains, from all clients, have been removed", () => { | ||
import("./smoldot-light").then(async (smoldot) => { | ||
import("./smoldot-light.js").then(async (smoldot) => { | ||
const { addWellKnownChain, addChain } = smoldot.createScClient() | ||
@@ -163,3 +163,3 @@ | ||
it("handles race conditions on the client when adding/removing chains", () => { | ||
import("./smoldot-light").then(async (smoldot) => { | ||
import("./smoldot-light.js").then(async (smoldot) => { | ||
const { addChain } = smoldot.createScClient() | ||
@@ -183,3 +183,3 @@ const { addChain: addChain2 } = smoldot.createScClient() | ||
it("propagates the correct chainSpec to smoldot", () => { | ||
import("./smoldot-light").then(async (smoldot) => { | ||
import("./smoldot-light.js").then(async (smoldot) => { | ||
const { addChain, addWellKnownChain } = smoldot.createScClient() | ||
@@ -214,3 +214,3 @@ const chainSpec = "testChainSpec" | ||
it("propagates the correct potentialRelayChainIds to smoldot", () => { | ||
import("./smoldot-light").then(async (smoldot) => { | ||
import("./smoldot-light.js").then(async (smoldot) => { | ||
const { addChain } = smoldot.createScClient() | ||
@@ -217,0 +217,0 @@ let prevChains = await Promise.all( |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
218626
4
34
2126
+ Added@noble/hashes@1.6.1(transitive)
+ Added@polkadot-api/client@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/json-rpc-provider@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/json-rpc-provider-proxy@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/light-client-extension-helpers@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/metadata-builders@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/substrate-bindings@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/substrate-client@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@polkadot-api/utils@0.0.1-56b4ad0a42cc57d8ef99401f196fcbe4691f8e41.1.0(transitive)
+ Added@scure/base@1.2.1(transitive)
+ Added@substrate/connect-extension-protocol@2.2.1(transitive)
+ Addedrxjs@7.8.1(transitive)
+ Addedscale-ts@1.6.1(transitive)
+ Addedtslib@2.8.1(transitive)