@multiplatform.one/wallet 7.7.2
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/README.md +8 -0
- package/dist/cjs/SiweKeycloakProvider.cjs +273 -0
- package/dist/cjs/SiweKeycloakProvider.native.js +309 -0
- package/dist/cjs/SiweKeycloakProvider.native.js.map +1 -0
- package/dist/cjs/base64.cjs +51 -0
- package/dist/cjs/base64.native.js +55 -0
- package/dist/cjs/base64.native.js.map +1 -0
- package/dist/cjs/base64.spec.cjs +57 -0
- package/dist/cjs/base64.spec.native.js +79 -0
- package/dist/cjs/base64.spec.native.js.map +1 -0
- package/dist/cjs/context.cjs +29 -0
- package/dist/cjs/context.native.js +32 -0
- package/dist/cjs/context.native.js.map +1 -0
- package/dist/cjs/hooks/context.cjs +29 -0
- package/dist/cjs/hooks/context.native.js +32 -0
- package/dist/cjs/hooks/context.native.js.map +1 -0
- package/dist/cjs/hooks/index.cjs +20 -0
- package/dist/cjs/hooks/index.native.js +23 -0
- package/dist/cjs/hooks/index.native.js.map +1 -0
- package/dist/cjs/hooks/useSiweKeycloak.cjs +61 -0
- package/dist/cjs/hooks/useSiweKeycloak.native.js +64 -0
- package/dist/cjs/hooks/useSiweKeycloak.native.js.map +1 -0
- package/dist/cjs/index.cjs +133 -0
- package/dist/cjs/index.native.js +189 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/render.cjs +78 -0
- package/dist/cjs/render.native.js +82 -0
- package/dist/cjs/render.native.js.map +1 -0
- package/dist/cjs/siwe.cjs +76 -0
- package/dist/cjs/siwe.native.js +94 -0
- package/dist/cjs/siwe.native.js.map +1 -0
- package/dist/cjs/types.cjs +18 -0
- package/dist/cjs/types.native.js +21 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/util.cjs +53 -0
- package/dist/cjs/util.native.js +56 -0
- package/dist/cjs/util.native.js.map +1 -0
- package/dist/cjs/util.spec.cjs +53 -0
- package/dist/cjs/util.spec.native.js +62 -0
- package/dist/cjs/util.spec.native.js.map +1 -0
- package/dist/esm/SiweKeycloakProvider.mjs +237 -0
- package/dist/esm/SiweKeycloakProvider.mjs.map +1 -0
- package/dist/esm/SiweKeycloakProvider.native.js +270 -0
- package/dist/esm/SiweKeycloakProvider.native.js.map +1 -0
- package/dist/esm/base64.mjs +26 -0
- package/dist/esm/base64.mjs.map +1 -0
- package/dist/esm/base64.native.js +27 -0
- package/dist/esm/base64.native.js.map +1 -0
- package/dist/esm/base64.spec.mjs +58 -0
- package/dist/esm/base64.spec.mjs.map +1 -0
- package/dist/esm/base64.spec.native.js +77 -0
- package/dist/esm/base64.spec.native.js.map +1 -0
- package/dist/esm/context.mjs +4 -0
- package/dist/esm/context.mjs.map +1 -0
- package/dist/esm/context.native.js +4 -0
- package/dist/esm/context.native.js.map +1 -0
- package/dist/esm/hooks/context.mjs +4 -0
- package/dist/esm/hooks/context.mjs.map +1 -0
- package/dist/esm/hooks/context.native.js +4 -0
- package/dist/esm/hooks/context.native.js.map +1 -0
- package/dist/esm/hooks/index.mjs +2 -0
- package/dist/esm/hooks/index.mjs.map +1 -0
- package/dist/esm/hooks/index.native.js +2 -0
- package/dist/esm/hooks/index.native.js.map +1 -0
- package/dist/esm/hooks/useSiweKeycloak.mjs +36 -0
- package/dist/esm/hooks/useSiweKeycloak.mjs.map +1 -0
- package/dist/esm/hooks/useSiweKeycloak.native.js +36 -0
- package/dist/esm/hooks/useSiweKeycloak.native.js.map +1 -0
- package/dist/esm/index.mjs +107 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +160 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/render.mjs +42 -0
- package/dist/esm/render.mjs.map +1 -0
- package/dist/esm/render.native.js +43 -0
- package/dist/esm/render.native.js.map +1 -0
- package/dist/esm/siwe.mjs +51 -0
- package/dist/esm/siwe.mjs.map +1 -0
- package/dist/esm/siwe.native.js +66 -0
- package/dist/esm/siwe.native.js.map +1 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/dist/esm/util.mjs +27 -0
- package/dist/esm/util.mjs.map +1 -0
- package/dist/esm/util.native.js +27 -0
- package/dist/esm/util.native.js.map +1 -0
- package/dist/esm/util.spec.mjs +54 -0
- package/dist/esm/util.spec.mjs.map +1 -0
- package/dist/esm/util.spec.native.js +60 -0
- package/dist/esm/util.spec.native.js.map +1 -0
- package/package.json +81 -0
- package/src/SiweKeycloakProvider.tsx +297 -0
- package/src/base64.spec.ts +65 -0
- package/src/base64.ts +26 -0
- package/src/context.ts +10 -0
- package/src/hooks/context.ts +10 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useSiweKeycloak.ts +23 -0
- package/src/index.ts +127 -0
- package/src/main/java/com/bitspur/keycloak/siwe/Errors.java +21 -0
- package/src/main/java/com/bitspur/keycloak/siwe/LiteAuthenticationService.java +167 -0
- package/src/main/java/com/bitspur/keycloak/siwe/SiweCredentials.java +36 -0
- package/src/main/java/com/bitspur/keycloak/siwe/SiweSession.java +36 -0
- package/src/main/java/com/bitspur/keycloak/siwe/SiweUtil.java +599 -0
- package/src/main/java/com/bitspur/keycloak/siwe/Token.java +85 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/browser/SiweBrowserAuthenticator.java +109 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/browser/SiweBrowserAuthenticatorFactory.java +93 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/directgrant/SiweDirectGrantAuthenticator.java +153 -0
- package/src/main/java/com/bitspur/keycloak/siwe/authenticators/directgrant/SiweDirectGrantAuthenticatorFactory.java +93 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/nonce/SiweNonce.java +95 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/nonce/SiweNonceJpaEntityProvider.java +82 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/nonce/SiweNonceJpaEntityProviderFactory.java +46 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/wallet/SiweWalletAddress.java +103 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/wallet/SiweWalletJpaEntityProvider.java +43 -0
- package/src/main/java/com/bitspur/keycloak/siwe/jpa/wallet/SiweWalletJpaEntityProviderFactory.java +46 -0
- package/src/main/java/com/bitspur/keycloak/siwe/protocol/SiweWalletProtocolMapper.java +85 -0
- package/src/main/java/com/bitspur/keycloak/siwe/resource/SiweResource.java +210 -0
- package/src/main/java/com/bitspur/keycloak/siwe/resource/SiweResourceProvider.java +34 -0
- package/src/main/java/com/bitspur/keycloak/siwe/resource/SiweResourceProviderFactory.java +46 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuCredentials.java +52 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuMessage.java +362 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuParseException.java +23 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/SiuUtil.java +547 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/browser/SiuBrowserAuthenticator.java +112 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/browser/SiuBrowserAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/directgrant/SiuDirectGrantAuthenticator.java +169 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/authenticators/directgrant/SiuDirectGrantAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/resource/SiuResource.java +244 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/resource/SiuResourceProvider.java +34 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siu/resource/SiuResourceProviderFactory.java +48 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsCredentials.java +52 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsMessage.java +362 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsParseException.java +23 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/SiwsUtil.java +564 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/browser/SiwsBrowserAuthenticator.java +112 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/browser/SiwsBrowserAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/directgrant/SiwsDirectGrantAuthenticator.java +169 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/authenticators/directgrant/SiwsDirectGrantAuthenticatorFactory.java +96 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/resource/SiwsResource.java +244 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/resource/SiwsResourceProvider.java +34 -0
- package/src/main/java/com/bitspur/keycloak/siwe/siws/resource/SiwsResourceProviderFactory.java +48 -0
- package/src/main/resources/META-INF/keycloak-themes.json +8 -0
- package/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +6 -0
- package/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory +2 -0
- package/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +1 -0
- package/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory +3 -0
- package/src/main/resources/META-INF/siwe-nonce-changelog.xml +29 -0
- package/src/main/resources/META-INF/siwe-wallet-changelog.xml +46 -0
- package/src/render.tsx +47 -0
- package/src/siwe.tsx +57 -0
- package/src/types.ts +6 -0
- package/src/util.spec.ts +72 -0
- package/src/util.ts +28 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SiweMessage } from "siwe";
|
|
2
|
+
async function getNonce(keycloakSiweUrl) {
|
|
3
|
+
var res = await fetch(`${keycloakSiweUrl}/nonce`, {
|
|
4
|
+
credentials: "include"
|
|
5
|
+
});
|
|
6
|
+
if (res.ok) {
|
|
7
|
+
var data = await res.text();
|
|
8
|
+
if (data === null || data === void 0 ? void 0 : data.length) return data;
|
|
9
|
+
}
|
|
10
|
+
throw new Error("failed to get nonce");
|
|
11
|
+
}
|
|
12
|
+
async function createMessage(nonce, address, chainId) {
|
|
13
|
+
if (typeof window === "undefined") {
|
|
14
|
+
throw new Error("createMessage requires browser context");
|
|
15
|
+
}
|
|
16
|
+
return new SiweMessage({
|
|
17
|
+
version: "1",
|
|
18
|
+
domain: window.location.host,
|
|
19
|
+
uri: window.location.origin,
|
|
20
|
+
address,
|
|
21
|
+
chainId,
|
|
22
|
+
nonce,
|
|
23
|
+
statement: "Sign in With Ethereum."
|
|
24
|
+
}).prepareMessage();
|
|
25
|
+
}
|
|
26
|
+
export { createMessage, getNonce };
|
|
27
|
+
//# sourceMappingURL=util.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["SiweMessage","getNonce","keycloakSiweUrl","res","fetch","credentials","ok","data","text","length","Error","createMessage","nonce","address","chainId","window","version","domain","location","host","uri","origin","statement","prepareMessage"],"sources":["util.native.js"],"sourcesContent":["import { SiweMessage } from \"siwe\";\nasync function getNonce(keycloakSiweUrl) {\n var res = await fetch(`${keycloakSiweUrl}/nonce`, {\n credentials: \"include\"\n });\n if (res.ok) {\n var data = await res.text();\n if (data === null || data === void 0 ? void 0 : data.length) return data;\n }\n throw new Error(\"failed to get nonce\");\n}\nasync function createMessage(nonce, address, chainId) {\n if (typeof window === \"undefined\") {\n throw new Error(\"createMessage requires browser context\");\n }\n return new SiweMessage({\n version: \"1\",\n domain: window.location.host,\n uri: window.location.origin,\n address,\n chainId,\n nonce,\n statement: \"Sign in With Ethereum.\"\n }).prepareMessage();\n}\nexport {\n createMessage,\n getNonce\n};\n//# sourceMappingURL=util.js.map\n"],"mappings":"AAAA,SAASA,WAAW,QAAQ,MAAM;AAClC,eAAeC,QAAQA,CAACC,eAAe,EAAE;EACvC,IAAIC,GAAG,GAAG,MAAMC,KAAK,CAAC,GAAGF,eAAe,QAAQ,EAAE;IAChDG,WAAW,EAAE;EACf,CAAC,CAAC;EACF,IAAIF,GAAG,CAACG,EAAE,EAAE;IACV,IAAIC,IAAI,GAAG,MAAMJ,GAAG,CAACK,IAAI,CAAC,CAAC;IAC3B,IAAID,IAAI,KAAK,IAAI,IAAIA,IAAI,KAAK,KAAK,CAAC,GAAG,KAAK,CAAC,GAAGA,IAAI,CAACE,MAAM,EAAE,OAAOF,IAAI;EAC1E;EACA,MAAM,IAAIG,KAAK,CAAC,qBAAqB,CAAC;AACxC;AACA,eAAeC,aAAaA,CAACC,KAAK,EAAEC,OAAO,EAAEC,OAAO,EAAE;EACpD,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE;IACjC,MAAM,IAAIL,KAAK,CAAC,wCAAwC,CAAC;EAC3D;EACA,OAAO,IAAIV,WAAW,CAAC;IACrBgB,OAAO,EAAE,GAAG;IACZC,MAAM,EAAEF,MAAM,CAACG,QAAQ,CAACC,IAAI;IAC5BC,GAAG,EAAEL,MAAM,CAACG,QAAQ,CAACG,MAAM;IAC3BR,OAAO;IACPC,OAAO;IACPF,KAAK;IACLU,SAAS,EAAE;EACb,CAAC,CAAC,CAACC,cAAc,CAAC,CAAC;AACrB;AACA,SACEZ,aAAa,EACbV,QAAQ","ignoreList":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { getNonce, createMessage } from "./util.mjs";
|
|
3
|
+
describe("getNonce", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
6
|
+
ok: true,
|
|
7
|
+
text: () => Promise.resolve("abc123nonce")
|
|
8
|
+
}));
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
vi.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
it("fetches nonce from the keycloak SIWE URL", async () => {
|
|
14
|
+
const nonce = await getNonce("https://auth.example.com/siwe");
|
|
15
|
+
expect(fetch).toHaveBeenCalledWith("https://auth.example.com/siwe/nonce", {
|
|
16
|
+
credentials: "include"
|
|
17
|
+
});
|
|
18
|
+
expect(nonce).toBe("abc123nonce");
|
|
19
|
+
});
|
|
20
|
+
it("throws when response is not ok", async () => {
|
|
21
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
22
|
+
ok: false,
|
|
23
|
+
text: () => Promise.resolve("")
|
|
24
|
+
}));
|
|
25
|
+
await expect(getNonce("https://auth.example.com/siwe")).rejects.toThrow("failed to get nonce");
|
|
26
|
+
});
|
|
27
|
+
it("throws when response body is empty", async () => {
|
|
28
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
29
|
+
ok: true,
|
|
30
|
+
text: () => Promise.resolve("")
|
|
31
|
+
}));
|
|
32
|
+
await expect(getNonce("https://auth.example.com/siwe")).rejects.toThrow("failed to get nonce");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe("createMessage", () => {
|
|
36
|
+
it("throws when window is undefined (non-browser)", async () => {
|
|
37
|
+
const origWindow = globalThis.window;
|
|
38
|
+
delete globalThis.window;
|
|
39
|
+
try {
|
|
40
|
+
await expect(createMessage("nonce123", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", 1)).rejects.toThrow("createMessage requires browser context");
|
|
41
|
+
} finally {
|
|
42
|
+
globalThis.window = origWindow;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it("creates a SIWE message using browser location", async () => {
|
|
46
|
+
const addr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
47
|
+
const message = await createMessage("nonce123", addr, 1);
|
|
48
|
+
expect(message).toContain("Sign in With Ethereum.");
|
|
49
|
+
expect(message).toContain("nonce123");
|
|
50
|
+
expect(message).toContain(addr);
|
|
51
|
+
expect(message).toContain("Chain ID: 1");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=util.spec.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["describe","expect","it","vi","beforeEach","afterEach","getNonce","createMessage","stubGlobal","fn","mockResolvedValue","ok","text","Promise","resolve","restoreAllMocks","nonce","fetch","toHaveBeenCalledWith","credentials","toBe","rejects","toThrow","origWindow","globalThis","window","addr","message","toContain"],"sources":["../../src/util.spec.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,EAAUC,MAAA,EAAQC,EAAA,EAAIC,EAAA,EAAIC,UAAA,EAAYC,SAAA,QAAiB;AAChE,SAASC,QAAA,EAAUC,aAAA,QAAqB;AAExCP,QAAA,CAAS,YAAY,MAAM;EACzBI,UAAA,CAAW,MAAM;IACfD,EAAA,CAAGK,UAAA,CACD,SACAL,EAAA,CAAGM,EAAA,CAAG,EAAEC,iBAAA,CAAkB;MACxBC,EAAA,EAAI;MACJC,IAAA,EAAMA,CAAA,KAAMC,OAAA,CAAQC,OAAA,CAAQ,aAAa;IAC3C,CAAC,CACH;EACF,CAAC;EAEDT,SAAA,CAAU,MAAM;IACdF,EAAA,CAAGY,eAAA,CAAgB;EACrB,CAAC;EAEDb,EAAA,CAAG,4CAA4C,YAAY;IACzD,MAAMc,KAAA,GAAQ,MAAMV,QAAA,CAAS,+BAA+B;IAC5DL,MAAA,CAAOgB,KAAK,EAAEC,oBAAA,CAAqB,uCAAuC;MACxEC,WAAA,EAAa;IACf,CAAC;IACDlB,MAAA,CAAOe,KAAK,EAAEI,IAAA,CAAK,aAAa;EAClC,CAAC;EAEDlB,EAAA,CAAG,kCAAkC,YAAY;IAC/CC,EAAA,CAAGK,UAAA,CACD,SACAL,EAAA,CAAGM,EAAA,CAAG,EAAEC,iBAAA,CAAkB;MACxBC,EAAA,EAAI;MACJC,IAAA,EAAMA,CAAA,KAAMC,OAAA,CAAQC,OAAA,CAAQ,EAAE;IAChC,CAAC,CACH;IACA,MAAMb,MAAA,CAAOK,QAAA,CAAS,+BAA+B,CAAC,EAAEe,OAAA,CAAQC,OAAA,CAAQ,qBAAqB;EAC/F,CAAC;EAEDpB,EAAA,CAAG,sCAAsC,YAAY;IACnDC,EAAA,CAAGK,UAAA,CACD,SACAL,EAAA,CAAGM,EAAA,CAAG,EAAEC,iBAAA,CAAkB;MACxBC,EAAA,EAAI;MACJC,IAAA,EAAMA,CAAA,KAAMC,OAAA,CAAQC,OAAA,CAAQ,EAAE;IAChC,CAAC,CACH;IACA,MAAMb,MAAA,CAAOK,QAAA,CAAS,+BAA+B,CAAC,EAAEe,OAAA,CAAQC,OAAA,CAAQ,qBAAqB;EAC/F,CAAC;AACH,CAAC;AAEDtB,QAAA,CAAS,iBAAiB,MAAM;EAC9BE,EAAA,CAAG,iDAAiD,YAAY;IAC9D,MAAMqB,UAAA,GAAaC,UAAA,CAAWC,MAAA;IAE9B,OAAOD,UAAA,CAAWC,MAAA;IAClB,IAAI;MACF,MAAMxB,MAAA,CACJM,aAAA,CAAc,YAAY,8CAA8C,CAAC,CAC3E,EAAEc,OAAA,CAAQC,OAAA,CAAQ,wCAAwC;IAC5D,UAAE;MACAE,UAAA,CAAWC,MAAA,GAASF,UAAA;IACtB;EACF,CAAC;EAEDrB,EAAA,CAAG,iDAAiD,YAAY;IAC9D,MAAMwB,IAAA,GAAO;IACb,MAAMC,OAAA,GAAU,MAAMpB,aAAA,CAAc,YAAYmB,IAAA,EAAM,CAAC;IACvDzB,MAAA,CAAO0B,OAAO,EAAEC,SAAA,CAAU,wBAAwB;IAClD3B,MAAA,CAAO0B,OAAO,EAAEC,SAAA,CAAU,UAAU;IACpC3B,MAAA,CAAO0B,OAAO,EAAEC,SAAA,CAAUF,IAAI;IAC9BzB,MAAA,CAAO0B,OAAO,EAAEC,SAAA,CAAU,aAAa;EACzC,CAAC;AACH,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { getNonce, createMessage } from "./util.native.js";
|
|
3
|
+
describe("getNonce", function () {
|
|
4
|
+
beforeEach(function () {
|
|
5
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
6
|
+
ok: true,
|
|
7
|
+
text: function text() {
|
|
8
|
+
return Promise.resolve("abc123nonce");
|
|
9
|
+
}
|
|
10
|
+
}));
|
|
11
|
+
});
|
|
12
|
+
afterEach(function () {
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
it("fetches nonce from the keycloak SIWE URL", async function () {
|
|
16
|
+
var nonce = await getNonce("https://auth.example.com/siwe");
|
|
17
|
+
expect(fetch).toHaveBeenCalledWith("https://auth.example.com/siwe/nonce", {
|
|
18
|
+
credentials: "include"
|
|
19
|
+
});
|
|
20
|
+
expect(nonce).toBe("abc123nonce");
|
|
21
|
+
});
|
|
22
|
+
it("throws when response is not ok", async function () {
|
|
23
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
24
|
+
ok: false,
|
|
25
|
+
text: function text() {
|
|
26
|
+
return Promise.resolve("");
|
|
27
|
+
}
|
|
28
|
+
}));
|
|
29
|
+
await expect(getNonce("https://auth.example.com/siwe")).rejects.toThrow("failed to get nonce");
|
|
30
|
+
});
|
|
31
|
+
it("throws when response body is empty", async function () {
|
|
32
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
33
|
+
ok: true,
|
|
34
|
+
text: function text() {
|
|
35
|
+
return Promise.resolve("");
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
await expect(getNonce("https://auth.example.com/siwe")).rejects.toThrow("failed to get nonce");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("createMessage", function () {
|
|
42
|
+
it("throws when window is undefined (non-browser)", async function () {
|
|
43
|
+
var origWindow = globalThis.window;
|
|
44
|
+
delete globalThis.window;
|
|
45
|
+
try {
|
|
46
|
+
await expect(createMessage("nonce123", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", 1)).rejects.toThrow("createMessage requires browser context");
|
|
47
|
+
} finally {
|
|
48
|
+
globalThis.window = origWindow;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
it("creates a SIWE message using browser location", async function () {
|
|
52
|
+
var addr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
53
|
+
var message = await createMessage("nonce123", addr, 1);
|
|
54
|
+
expect(message).toContain("Sign in With Ethereum.");
|
|
55
|
+
expect(message).toContain("nonce123");
|
|
56
|
+
expect(message).toContain(addr);
|
|
57
|
+
expect(message).toContain("Chain ID: 1");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=util.spec.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["describe","expect","it","vi","beforeEach","afterEach","getNonce","createMessage","stubGlobal","fn","mockResolvedValue","ok","text","Promise","resolve","restoreAllMocks","nonce","fetch","toHaveBeenCalledWith","credentials","toBe","rejects","toThrow","origWindow","globalThis","window","addr","message","toContain"],"sources":["util.spec.native.js"],"sourcesContent":["import { describe, expect, it, vi, beforeEach, afterEach } from \"vitest\";\nimport { getNonce, createMessage } from \"./util\";\ndescribe(\"getNonce\", function() {\n beforeEach(function() {\n vi.stubGlobal(\"fetch\", vi.fn().mockResolvedValue({\n ok: true,\n text: function text() {\n return Promise.resolve(\"abc123nonce\");\n }\n }));\n });\n afterEach(function() {\n vi.restoreAllMocks();\n });\n it(\"fetches nonce from the keycloak SIWE URL\", async function() {\n var nonce = await getNonce(\"https://auth.example.com/siwe\");\n expect(fetch).toHaveBeenCalledWith(\"https://auth.example.com/siwe/nonce\", {\n credentials: \"include\"\n });\n expect(nonce).toBe(\"abc123nonce\");\n });\n it(\"throws when response is not ok\", async function() {\n vi.stubGlobal(\"fetch\", vi.fn().mockResolvedValue({\n ok: false,\n text: function text() {\n return Promise.resolve(\"\");\n }\n }));\n await expect(getNonce(\"https://auth.example.com/siwe\")).rejects.toThrow(\"failed to get nonce\");\n });\n it(\"throws when response body is empty\", async function() {\n vi.stubGlobal(\"fetch\", vi.fn().mockResolvedValue({\n ok: true,\n text: function text() {\n return Promise.resolve(\"\");\n }\n }));\n await expect(getNonce(\"https://auth.example.com/siwe\")).rejects.toThrow(\"failed to get nonce\");\n });\n});\ndescribe(\"createMessage\", function() {\n it(\"throws when window is undefined (non-browser)\", async function() {\n var origWindow = globalThis.window;\n delete globalThis.window;\n try {\n await expect(createMessage(\"nonce123\", \"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\", 1)).rejects.toThrow(\"createMessage requires browser context\");\n } finally {\n globalThis.window = origWindow;\n }\n });\n it(\"creates a SIWE message using browser location\", async function() {\n var addr = \"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\";\n var message = await createMessage(\"nonce123\", addr, 1);\n expect(message).toContain(\"Sign in With Ethereum.\");\n expect(message).toContain(\"nonce123\");\n expect(message).toContain(addr);\n expect(message).toContain(\"Chain ID: 1\");\n });\n});\n//# sourceMappingURL=util.spec.js.map\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@multiplatform.one/wallet",
|
|
3
|
+
"version": "7.7.2",
|
|
4
|
+
"description": "siwe keycloak client",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"4361",
|
|
7
|
+
"erc-4361",
|
|
8
|
+
"ethereum",
|
|
9
|
+
"keycloak",
|
|
10
|
+
"siwe",
|
|
11
|
+
"wallet"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://multiplatform.one",
|
|
14
|
+
"license": "Apache-2.0",
|
|
15
|
+
"author": "BitSpur",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://gitlab.com/bitspur/frappe/multiplatform.one.git",
|
|
19
|
+
"directory": "public/wallet"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src",
|
|
23
|
+
"types",
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "dist/cjs/index.cjs",
|
|
28
|
+
"module": "dist/esm/index.mjs",
|
|
29
|
+
"types": "./types/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
"./package.json": "./package.json",
|
|
32
|
+
".": {
|
|
33
|
+
"source": "./src/index.ts",
|
|
34
|
+
"react-native": "./src/index.ts",
|
|
35
|
+
"types": "./types/index.d.ts",
|
|
36
|
+
"import": "./dist/esm/index.mjs",
|
|
37
|
+
"require": "./dist/cjs/index.cjs",
|
|
38
|
+
"default": "./dist/cjs/index.native.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "rm -rf dist types 2>/dev/null && tamagui-build",
|
|
46
|
+
"test": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=text --coverage.reporter=lcov --coverage.reporter=html",
|
|
47
|
+
"typecheck": "tsgo --noEmit"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@multiplatform.one/keycloak-js": "7.7.2",
|
|
51
|
+
"@multiplatform.one/store": "7.7.2",
|
|
52
|
+
"@multiplatform.one/web3": "7.7.2",
|
|
53
|
+
"siwe": "^3.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@tamagui/build": "2.7.6",
|
|
57
|
+
"@tanstack/react-query": "^5.100.7",
|
|
58
|
+
"@types/js-cookie": "^3.0.6",
|
|
59
|
+
"@types/node": "^25.6.0",
|
|
60
|
+
"@types/react-dom": "^19.2.3",
|
|
61
|
+
"@wagmi/connectors": "^7.2.1",
|
|
62
|
+
"@wagmi/core": "^3.4.8",
|
|
63
|
+
"react": "19.2.5",
|
|
64
|
+
"react-dom": "19.2.5",
|
|
65
|
+
"react-native-web": "^0.21.2",
|
|
66
|
+
"typescript": "~5.9.3",
|
|
67
|
+
"viem": "^2.48.7",
|
|
68
|
+
"wagmi": "^3.7.1"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@tanstack/react-query": "^5.62.16",
|
|
72
|
+
"@wagmi/connectors": "^7.2.1",
|
|
73
|
+
"@wagmi/core": "^3.4.0",
|
|
74
|
+
"react": "^19.1.0",
|
|
75
|
+
"viem": "^2.47.5",
|
|
76
|
+
"wagmi": "^3.5.0"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=22.0.0"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import Keycloak from "@multiplatform.one/keycloak-js";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
|
+
import type { CreateConfigParameters } from "@wagmi/core";
|
|
4
|
+
import type { SIWEConfig } from "@multiplatform.one/web3/connectkit";
|
|
5
|
+
import {
|
|
6
|
+
ConnectKitProvider,
|
|
7
|
+
SIWEProvider,
|
|
8
|
+
createWalletMessage,
|
|
9
|
+
getDefaultConfig,
|
|
10
|
+
} from "@multiplatform.one/web3/connectkit";
|
|
11
|
+
import type { PropsWithChildren } from "react";
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { WagmiProvider, createConfig } from "wagmi";
|
|
14
|
+
import { arbitrum, mainnet, optimism, polygon } from "wagmi/chains";
|
|
15
|
+
import type { CoinbaseWalletParameters } from "wagmi/connectors";
|
|
16
|
+
import { storage } from "@multiplatform.one/store";
|
|
17
|
+
import { SiweKeycloakContext } from "./hooks/context";
|
|
18
|
+
import type { KeycloakConfig } from "./types";
|
|
19
|
+
import { encodeBase64 } from "./base64";
|
|
20
|
+
import { getNonce } from "./util";
|
|
21
|
+
|
|
22
|
+
export interface Tokens {
|
|
23
|
+
accessToken: string;
|
|
24
|
+
idToken: string;
|
|
25
|
+
refreshToken: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SiweKeycloakProviderProps extends PropsWithChildren {
|
|
29
|
+
appDescription?: string;
|
|
30
|
+
appIcon?: string;
|
|
31
|
+
appName?: string;
|
|
32
|
+
appUrl?: string;
|
|
33
|
+
clearTokens?: () => void | Promise<void>;
|
|
34
|
+
coinbaseWalletPreference?: CoinbaseWalletParameters<"4">["preference"];
|
|
35
|
+
debug?: boolean;
|
|
36
|
+
getTokens?: () => Tokens | Promise<Tokens>;
|
|
37
|
+
keycloak?: Partial<KeycloakConfig>;
|
|
38
|
+
onSignedIn?: () => any;
|
|
39
|
+
onSignedInFailed?: (err: Error) => any;
|
|
40
|
+
setTokens?: (tokens: Tokens) => void | Promise<void>;
|
|
41
|
+
siwe?: boolean;
|
|
42
|
+
wagmi?: Partial<CreateConfigParameters>;
|
|
43
|
+
walletConnectProjectId?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const queryClient = new QueryClient();
|
|
47
|
+
const logger = console;
|
|
48
|
+
const ACCESS_TOKEN_KEY = "KEYCLOAK_TOKEN";
|
|
49
|
+
const ID_TOKEN_KEY = "KEYCLOAK_ID_TOKEN";
|
|
50
|
+
const REFRESH_TOKEN_KEY = "KEYCLOAK_REFRESH_TOKEN";
|
|
51
|
+
|
|
52
|
+
export function SiweKeycloakProvider({
|
|
53
|
+
children,
|
|
54
|
+
clearTokens: clearTokensProp,
|
|
55
|
+
debug,
|
|
56
|
+
getTokens: getTokensProp,
|
|
57
|
+
keycloak: keycloakProp,
|
|
58
|
+
siwe,
|
|
59
|
+
setTokens: setTokensProp,
|
|
60
|
+
wagmi: wagmiProp,
|
|
61
|
+
onSignedIn,
|
|
62
|
+
onSignedInFailed,
|
|
63
|
+
...props
|
|
64
|
+
}: SiweKeycloakProviderProps) {
|
|
65
|
+
const keycloakConfig = useMemo(
|
|
66
|
+
() => ({
|
|
67
|
+
...keycloakProp,
|
|
68
|
+
...(typeof keycloakProp?.baseUrl !== "undefined"
|
|
69
|
+
? { baseUrl: keycloakProp.baseUrl }
|
|
70
|
+
: {
|
|
71
|
+
baseUrl: "http://localhost:8080",
|
|
72
|
+
}),
|
|
73
|
+
...(typeof keycloakProp?.clientId !== "undefined"
|
|
74
|
+
? { clientId: keycloakProp.clientId }
|
|
75
|
+
: { clientId: "app" }),
|
|
76
|
+
...(typeof keycloakProp?.realm !== "undefined"
|
|
77
|
+
? { realm: keycloakProp.realm }
|
|
78
|
+
: { realm: "main" }),
|
|
79
|
+
...(typeof keycloakProp?.scope !== "undefined"
|
|
80
|
+
? { scope: keycloakProp.scope }
|
|
81
|
+
: { scope: [] }),
|
|
82
|
+
}),
|
|
83
|
+
[keycloakProp],
|
|
84
|
+
);
|
|
85
|
+
const keycloak = useMemo(
|
|
86
|
+
() =>
|
|
87
|
+
typeof window !== "undefined"
|
|
88
|
+
? new Keycloak({
|
|
89
|
+
clientId: keycloakConfig.clientId,
|
|
90
|
+
realm: keycloakConfig.realm,
|
|
91
|
+
url: keycloakConfig.baseUrl,
|
|
92
|
+
})
|
|
93
|
+
: undefined,
|
|
94
|
+
[keycloakConfig],
|
|
95
|
+
);
|
|
96
|
+
const clearTokens = useMemo(
|
|
97
|
+
() =>
|
|
98
|
+
clearTokensProp ||
|
|
99
|
+
(async () => {
|
|
100
|
+
await Promise.resolve(storage.removeItem(ID_TOKEN_KEY));
|
|
101
|
+
await Promise.resolve(storage.removeItem(REFRESH_TOKEN_KEY));
|
|
102
|
+
await Promise.resolve(storage.removeItem(ACCESS_TOKEN_KEY));
|
|
103
|
+
}),
|
|
104
|
+
[clearTokensProp],
|
|
105
|
+
);
|
|
106
|
+
const setTokens = useMemo(
|
|
107
|
+
() =>
|
|
108
|
+
setTokensProp ||
|
|
109
|
+
(async ({ accessToken, refreshToken, idToken }: Tokens) => {
|
|
110
|
+
await Promise.resolve(storage.setItem(ID_TOKEN_KEY, idToken));
|
|
111
|
+
await Promise.resolve(storage.setItem(REFRESH_TOKEN_KEY, refreshToken));
|
|
112
|
+
await Promise.resolve(storage.setItem(ACCESS_TOKEN_KEY, accessToken));
|
|
113
|
+
}),
|
|
114
|
+
[setTokensProp],
|
|
115
|
+
);
|
|
116
|
+
const getTokens = useMemo(
|
|
117
|
+
() =>
|
|
118
|
+
getTokensProp ||
|
|
119
|
+
(async () => ({
|
|
120
|
+
accessToken: (await Promise.resolve(storage.getItem(ACCESS_TOKEN_KEY))) || "",
|
|
121
|
+
idToken: (await Promise.resolve(storage.getItem(ID_TOKEN_KEY))) || "",
|
|
122
|
+
refreshToken: (await Promise.resolve(storage.getItem(REFRESH_TOKEN_KEY))) || "",
|
|
123
|
+
})),
|
|
124
|
+
[getTokensProp],
|
|
125
|
+
);
|
|
126
|
+
const keycloakRealmUrl = useMemo(
|
|
127
|
+
() => `${keycloakConfig.baseUrl}/realms/${keycloakConfig.realm || "main"}`,
|
|
128
|
+
[keycloakConfig],
|
|
129
|
+
);
|
|
130
|
+
const keycloakSiweUrl = useMemo(() => `${keycloakRealmUrl}/siwe`, [keycloakRealmUrl]);
|
|
131
|
+
const config = useMemo(
|
|
132
|
+
() =>
|
|
133
|
+
createConfig(
|
|
134
|
+
getDefaultConfig({
|
|
135
|
+
appName: "Example",
|
|
136
|
+
chains: [mainnet, polygon, optimism, arbitrum],
|
|
137
|
+
walletConnectProjectId: props.appName || "Example",
|
|
138
|
+
...props,
|
|
139
|
+
...wagmiProp,
|
|
140
|
+
}),
|
|
141
|
+
),
|
|
142
|
+
[props, wagmiProp],
|
|
143
|
+
);
|
|
144
|
+
const siweConfig = useMemo(() => {
|
|
145
|
+
const siweConfig: SIWEConfig = {
|
|
146
|
+
getNonce: async () => getNonce(keycloakSiweUrl),
|
|
147
|
+
// Chain-agnostic: derives the signer family from the active connector's
|
|
148
|
+
// (possibly synthetic) chainId and builds the matching SIWx message, so EVM
|
|
149
|
+
// and Solana/Sui/Bitcoin all sign in through ConnectKit's one signIn().
|
|
150
|
+
createMessage({ nonce, address, chainId }) {
|
|
151
|
+
return createWalletMessage({ address, chainId, nonce });
|
|
152
|
+
},
|
|
153
|
+
async verifyMessage({ message, signature }) {
|
|
154
|
+
const formData = new URLSearchParams({
|
|
155
|
+
client_id: keycloakConfig.clientId,
|
|
156
|
+
grant_type: "password",
|
|
157
|
+
password: encodeBase64(message),
|
|
158
|
+
username: signature,
|
|
159
|
+
scope: [
|
|
160
|
+
"openid",
|
|
161
|
+
[
|
|
162
|
+
...(Array.isArray(keycloakConfig.scope)
|
|
163
|
+
? keycloakConfig.scope
|
|
164
|
+
: keycloakConfig.scope
|
|
165
|
+
? [keycloakConfig.scope]
|
|
166
|
+
: []),
|
|
167
|
+
],
|
|
168
|
+
].join(" "),
|
|
169
|
+
});
|
|
170
|
+
const res = await fetch(`${keycloakRealmUrl}/protocol/openid-connect/token`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
credentials: "include",
|
|
173
|
+
headers: {
|
|
174
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
175
|
+
},
|
|
176
|
+
body: formData,
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
const error = new Error(`sign in failed with status ${res.status}`);
|
|
180
|
+
logger.warn(error);
|
|
181
|
+
onSignedInFailed?.(error);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const data = await res.json();
|
|
185
|
+
if (!data.access_token || !data.id_token || !data.refresh_token) {
|
|
186
|
+
const error = new Error(
|
|
187
|
+
`missing tokens ${[
|
|
188
|
+
!data.access_token && "access_token",
|
|
189
|
+
!data.id_token && "id_token",
|
|
190
|
+
!data.refresh_token && "refresh_token",
|
|
191
|
+
]
|
|
192
|
+
.filter(Boolean)
|
|
193
|
+
.reduce((acc, curr, i, arr) => {
|
|
194
|
+
if (i === 0) return curr;
|
|
195
|
+
if (i === arr.length - 1) return `${acc} and ${curr}`;
|
|
196
|
+
return `${acc}, ${curr}`;
|
|
197
|
+
}, "")}`,
|
|
198
|
+
);
|
|
199
|
+
logger.warn(error);
|
|
200
|
+
onSignedInFailed?.(error);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
await Promise.resolve(
|
|
204
|
+
setTokens({
|
|
205
|
+
accessToken: data.access_token,
|
|
206
|
+
idToken: data.id_token,
|
|
207
|
+
refreshToken: data.refresh_token,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
onSignedIn?.();
|
|
211
|
+
return true;
|
|
212
|
+
},
|
|
213
|
+
async getSession() {
|
|
214
|
+
if (!keycloak) return null;
|
|
215
|
+
const { accessToken, refreshToken, idToken } = await Promise.resolve(getTokens());
|
|
216
|
+
await keycloak.init({
|
|
217
|
+
checkLoginIframe: false,
|
|
218
|
+
flow: "implicit",
|
|
219
|
+
idToken,
|
|
220
|
+
onLoad: undefined,
|
|
221
|
+
refreshToken,
|
|
222
|
+
timeSkew: 0,
|
|
223
|
+
token: accessToken,
|
|
224
|
+
});
|
|
225
|
+
if (
|
|
226
|
+
keycloak.token !== accessToken ||
|
|
227
|
+
keycloak.refreshToken !== refreshToken ||
|
|
228
|
+
keycloak.idToken !== idToken
|
|
229
|
+
) {
|
|
230
|
+
await Promise.resolve(
|
|
231
|
+
setTokens({
|
|
232
|
+
accessToken: keycloak.token || accessToken,
|
|
233
|
+
idToken: keycloak.idToken || idToken,
|
|
234
|
+
refreshToken: keycloak.refreshToken || refreshToken,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const res = await fetch(`${keycloakSiweUrl}/session`, {
|
|
240
|
+
headers: {
|
|
241
|
+
Authorization: `Bearer ${keycloak.token}`,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
if (res.ok) return res.json();
|
|
245
|
+
if (res.status === 401) return null;
|
|
246
|
+
throw new Error("Failed to get session");
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (err instanceof Error && err.message === "Failed to get session") {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
async signOut() {
|
|
255
|
+
if (!keycloak) return false;
|
|
256
|
+
keycloak.idToken = undefined;
|
|
257
|
+
keycloak.refreshToken = undefined;
|
|
258
|
+
keycloak.token = undefined;
|
|
259
|
+
await Promise.resolve(clearTokens());
|
|
260
|
+
return true;
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
return siweConfig;
|
|
264
|
+
}, [keycloakRealmUrl, keycloakSiweUrl, keycloakConfig]);
|
|
265
|
+
|
|
266
|
+
if (siwe) {
|
|
267
|
+
return (
|
|
268
|
+
<WagmiProvider config={config}>
|
|
269
|
+
<QueryClientProvider client={queryClient}>
|
|
270
|
+
<SIWEProvider
|
|
271
|
+
signOutOnAccountChange={false}
|
|
272
|
+
signOutOnDisconnect={false}
|
|
273
|
+
signOutOnNetworkChange={false}
|
|
274
|
+
{...siweConfig}
|
|
275
|
+
>
|
|
276
|
+
<ConnectKitProvider debugMode={debug}>
|
|
277
|
+
<SiweKeycloakContext.Provider value={{ keycloak }}>
|
|
278
|
+
{children}
|
|
279
|
+
</SiweKeycloakContext.Provider>
|
|
280
|
+
</ConnectKitProvider>
|
|
281
|
+
</SIWEProvider>
|
|
282
|
+
</QueryClientProvider>
|
|
283
|
+
</WagmiProvider>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return (
|
|
287
|
+
<WagmiProvider config={config}>
|
|
288
|
+
<QueryClientProvider client={queryClient}>
|
|
289
|
+
<ConnectKitProvider debugMode={debug}>
|
|
290
|
+
<SiweKeycloakContext.Provider value={{ keycloak }}>
|
|
291
|
+
{children}
|
|
292
|
+
</SiweKeycloakContext.Provider>
|
|
293
|
+
</ConnectKitProvider>
|
|
294
|
+
</QueryClientProvider>
|
|
295
|
+
</WagmiProvider>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it, vi, afterEach } from "vitest";
|
|
2
|
+
import { encodeBase64 } from "./base64";
|
|
3
|
+
|
|
4
|
+
describe("encodeBase64", () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("encodes an empty string", () => {
|
|
10
|
+
expect(encodeBase64("")).toBe("");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("encodes a simple ASCII string", () => {
|
|
14
|
+
expect(encodeBase64("Hello")).toBe("SGVsbG8=");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("encodes 'Hello, World!'", () => {
|
|
18
|
+
expect(encodeBase64("Hello, World!")).toBe("SGVsbG8sIFdvcmxkIQ==");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("encodes a single character", () => {
|
|
22
|
+
expect(encodeBase64("A")).toBe("QQ==");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("encodes two characters (padding = 1)", () => {
|
|
26
|
+
expect(encodeBase64("AB")).toBe("QUI=");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("encodes three characters (no padding)", () => {
|
|
30
|
+
expect(encodeBase64("ABC")).toBe("QUJD");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("encodes strings with special characters", () => {
|
|
34
|
+
expect(encodeBase64("foo:bar")).toBe("Zm9vOmJhcg==");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("encodes strings with numbers", () => {
|
|
38
|
+
expect(encodeBase64("12345")).toBe("MTIzNDU=");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("produces output matching btoa for ASCII input", () => {
|
|
42
|
+
const inputs = ["test", "a", "ab", "abc", "abcd", "Hello, World!", "base64 encoding test"];
|
|
43
|
+
for (const input of inputs) {
|
|
44
|
+
expect(encodeBase64(input)).toBe(btoa(input));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("falls back to manual encoding when btoa is unavailable", () => {
|
|
49
|
+
const originalBtoa = globalThis.btoa;
|
|
50
|
+
vi.spyOn(globalThis, "btoa").mockImplementation(() => {
|
|
51
|
+
throw new Error("btoa not available");
|
|
52
|
+
});
|
|
53
|
+
// Force the fallback path by making btoa throw (typeof check still passes)
|
|
54
|
+
// We need to actually remove it
|
|
55
|
+
Object.defineProperty(globalThis, "btoa", { value: undefined, configurable: true });
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
expect(encodeBase64("Hello")).toBe("SGVsbG8=");
|
|
59
|
+
expect(encodeBase64("ABC")).toBe("QUJD");
|
|
60
|
+
expect(encodeBase64("")).toBe("");
|
|
61
|
+
} finally {
|
|
62
|
+
Object.defineProperty(globalThis, "btoa", { value: originalBtoa, configurable: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/base64.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2
|
+
|
|
3
|
+
function encodeBytes(bytes: Uint8Array): string {
|
|
4
|
+
let output = "";
|
|
5
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
6
|
+
const a = bytes[i] ?? 0;
|
|
7
|
+
const b = bytes[i + 1] ?? 0;
|
|
8
|
+
const c = bytes[i + 2] ?? 0;
|
|
9
|
+
const triple = (a << 16) | (b << 8) | c;
|
|
10
|
+
output += alphabet[(triple >> 18) & 0x3f];
|
|
11
|
+
output += alphabet[(triple >> 12) & 0x3f];
|
|
12
|
+
output += i + 1 < bytes.length ? alphabet[(triple >> 6) & 0x3f] : "=";
|
|
13
|
+
output += i + 2 < bytes.length ? alphabet[triple & 0x3f] : "=";
|
|
14
|
+
}
|
|
15
|
+
return output;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function encodeBase64(input: string): string {
|
|
19
|
+
if (typeof globalThis.btoa === "function") {
|
|
20
|
+
return globalThis.btoa(input);
|
|
21
|
+
}
|
|
22
|
+
if (typeof Buffer !== "undefined") {
|
|
23
|
+
return Buffer.from(input, "utf-8").toString("base64");
|
|
24
|
+
}
|
|
25
|
+
return encodeBytes(new TextEncoder().encode(input));
|
|
26
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type Keycloak from "@multiplatform.one/keycloak-js";
|
|
2
|
+
import { createContext } from "react";
|
|
3
|
+
|
|
4
|
+
interface SiweKeycloakContextValue {
|
|
5
|
+
keycloak?: Keycloak;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const SiweKeycloakContext = createContext<SiweKeycloakContextValue>(
|
|
9
|
+
{} as SiweKeycloakContextValue,
|
|
10
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type Keycloak from "@multiplatform.one/keycloak-js";
|
|
2
|
+
import { createContext } from "react";
|
|
3
|
+
|
|
4
|
+
interface SiweKeycloakContextValue {
|
|
5
|
+
keycloak?: Keycloak;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const SiweKeycloakContext = createContext<SiweKeycloakContextValue>(
|
|
9
|
+
{} as SiweKeycloakContextValue,
|
|
10
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useSiweKeycloak";
|