shogun-button-react 6.4.0 β 6.4.1
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 +17 -0
- package/dist/components/ShogunButton.js +177 -9
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/118.index.js +0 -2
- package/dist/118.index.js.LICENSE.txt +0 -3
- package/dist/18.index.js +0 -1
- package/dist/195.index.js +0 -2
- package/dist/195.index.js.LICENSE.txt +0 -1
- package/dist/452.index.js +0 -2
- package/dist/452.index.js.LICENSE.txt +0 -1
- package/dist/508.index.js +0 -2
- package/dist/508.index.js.LICENSE.txt +0 -1
- package/dist/604.index.js +0 -2
- package/dist/604.index.js.LICENSE.txt +0 -7
- package/dist/619.index.js +0 -2
- package/dist/619.index.js.LICENSE.txt +0 -1
- package/dist/639.index.js +0 -2
- package/dist/639.index.js.LICENSE.txt +0 -1
- package/dist/695.index.js +0 -1
- package/dist/725.index.js +0 -2
- package/dist/725.index.js.LICENSE.txt +0 -1
- package/dist/732.index.js +0 -1
- package/dist/766.index.js +0 -2
- package/dist/766.index.js.LICENSE.txt +0 -1
- package/dist/841.index.js +0 -2
- package/dist/841.index.js.LICENSE.txt +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ A comprehensive React component library for seamless integration of Shogun authe
|
|
|
9
9
|
- π **Easy Integration** - Simple setup with minimal configuration
|
|
10
10
|
- π¨ **Customizable UI** - Modern, responsive design with dark mode support
|
|
11
11
|
- π **Multi-Authentication** - Support for Password, MetaMask, WebAuthn, Nostr, and ZK-Proof
|
|
12
|
+
- π‘οΈ **WebAuthn Recovery** - Restore hardware credentials on new devices with saved seed phrases
|
|
12
13
|
- π **Account Management** - Export/import Gun pairs for account backup and recovery
|
|
14
|
+
- π΅οΈ **ZK-Proof Trapdoor Handoff** - Display and copy the generated trapdoor during signup to keep anonymous identities portable
|
|
13
15
|
- π± **Responsive Design** - Works seamlessly across all device sizes
|
|
14
16
|
- π **TypeScript Support** - Full type safety and IntelliSense support
|
|
15
17
|
- π **Plugin System** - Advanced Gun operations with custom hooks
|
|
@@ -170,6 +172,21 @@ const { core, options } = shogunConnector({
|
|
|
170
172
|
});
|
|
171
173
|
```
|
|
172
174
|
|
|
175
|
+
## π Recovery Flows
|
|
176
|
+
|
|
177
|
+
### WebAuthn Multi-Device Restore
|
|
178
|
+
|
|
179
|
+
- Users now see a **Restore with Recovery Code** option when choosing WebAuthn login.
|
|
180
|
+
- Enter the username plus the stored seed phrase to recreate the credential on a new browser.
|
|
181
|
+
- The button calls `webauthnPlugin.signUp(username, { seedPhrase, generateSeedPhrase: false })` behind the scenes, leveraging the core pluginβs `importFromSeed` flow.
|
|
182
|
+
- After a successful restore the seed phrase is shown one more time so the user can double-check or re-copy it before the modal closes.
|
|
183
|
+
|
|
184
|
+
### ZK-Proof Trapdoor Delivery
|
|
185
|
+
|
|
186
|
+
- Upon successful `zkproof` signup, the modal switches to a confirmation screen that displays the generated trapdoor (also returned as `seedPhrase`).
|
|
187
|
+
- A **Copy Trapdoor** helper copies the phrase to the clipboard, with inline feedback when the copy succeeds.
|
|
188
|
+
- The user must acknowledge with **I Saved My Trapdoor** before returning to the main UI, reducing the risk of losing the anonymous identity.
|
|
189
|
+
|
|
173
190
|
## π― API Reference
|
|
174
191
|
|
|
175
192
|
### ShogunButtonProvider
|
|
@@ -220,7 +220,7 @@ export function ShogunButtonProvider({ children, core, options, onLoginSuccess,
|
|
|
220
220
|
};
|
|
221
221
|
// Unified signup
|
|
222
222
|
const signUp = async (method, ...args) => {
|
|
223
|
-
var _a, _b;
|
|
223
|
+
var _a, _b, _c;
|
|
224
224
|
try {
|
|
225
225
|
if (!core) {
|
|
226
226
|
throw new Error("SDK not initialized");
|
|
@@ -252,18 +252,39 @@ export function ShogunButtonProvider({ children, core, options, onLoginSuccess,
|
|
|
252
252
|
throw error;
|
|
253
253
|
}
|
|
254
254
|
break;
|
|
255
|
-
case "webauthn":
|
|
256
|
-
username = args[0];
|
|
255
|
+
case "webauthn": {
|
|
256
|
+
username = typeof args[0] === "string" ? args[0].trim() : "";
|
|
257
|
+
const webauthnOptions = args.length > 1 && typeof args[1] === "object" && args[1] !== null
|
|
258
|
+
? args[1]
|
|
259
|
+
: {};
|
|
260
|
+
if (!username) {
|
|
261
|
+
throw new Error("Username is required for WebAuthn registration");
|
|
262
|
+
}
|
|
257
263
|
if (isShogunCore(core)) {
|
|
258
264
|
const webauthn = core.getPlugin("webauthn");
|
|
259
265
|
if (!webauthn)
|
|
260
266
|
throw new Error("WebAuthn plugin not available");
|
|
261
|
-
|
|
267
|
+
const pluginOptions = {};
|
|
268
|
+
if (webauthnOptions.seedPhrase) {
|
|
269
|
+
pluginOptions.seedPhrase = webauthnOptions.seedPhrase.trim();
|
|
270
|
+
pluginOptions.generateSeedPhrase =
|
|
271
|
+
(_a = webauthnOptions.generateSeedPhrase) !== null && _a !== void 0 ? _a : false;
|
|
272
|
+
}
|
|
273
|
+
else if (typeof webauthnOptions.generateSeedPhrase === "boolean") {
|
|
274
|
+
pluginOptions.generateSeedPhrase =
|
|
275
|
+
webauthnOptions.generateSeedPhrase;
|
|
276
|
+
}
|
|
277
|
+
if (pluginOptions.generateSeedPhrase === undefined &&
|
|
278
|
+
!pluginOptions.seedPhrase) {
|
|
279
|
+
pluginOptions.generateSeedPhrase = true;
|
|
280
|
+
}
|
|
281
|
+
result = await webauthn.signUp(username, pluginOptions);
|
|
262
282
|
}
|
|
263
283
|
else {
|
|
264
284
|
throw new Error("WebAuthn requires ShogunCore");
|
|
265
285
|
}
|
|
266
286
|
break;
|
|
287
|
+
}
|
|
267
288
|
case "web3":
|
|
268
289
|
if (isShogunCore(core)) {
|
|
269
290
|
const web3 = core.getPlugin("web3");
|
|
@@ -320,7 +341,7 @@ export function ShogunButtonProvider({ children, core, options, onLoginSuccess,
|
|
|
320
341
|
if (result.success) {
|
|
321
342
|
let userPub = result.userPub || "";
|
|
322
343
|
if (!userPub && isShogunCore(core)) {
|
|
323
|
-
userPub = ((
|
|
344
|
+
userPub = ((_c = (_b = core.gun.user()) === null || _b === void 0 ? void 0 : _b.is) === null || _c === void 0 ? void 0 : _c.pub) || "";
|
|
324
345
|
}
|
|
325
346
|
const displayName = result.alias || username || userPub.slice(0, 8) + "...";
|
|
326
347
|
setIsLoggedIn(true);
|
|
@@ -616,7 +637,10 @@ export const ShogunButton = (() => {
|
|
|
616
637
|
const [showCopySuccess, setShowCopySuccess] = useState(false);
|
|
617
638
|
const [showImportSuccess, setShowImportSuccess] = useState(false);
|
|
618
639
|
const [zkTrapdoor, setZkTrapdoor] = useState("");
|
|
640
|
+
const [zkSignupTrapdoor, setZkSignupTrapdoor] = useState("");
|
|
641
|
+
const [showZkTrapdoorCopySuccess, setShowZkTrapdoorCopySuccess] = useState(false);
|
|
619
642
|
const [webauthnSeedPhrase, setWebauthnSeedPhrase] = useState("");
|
|
643
|
+
const [webauthnRecoverySeed, setWebauthnRecoverySeed] = useState("");
|
|
620
644
|
const dropdownRef = useRef(null);
|
|
621
645
|
// Handle click outside to close dropdown
|
|
622
646
|
useEffect(() => {
|
|
@@ -753,6 +777,41 @@ export const ShogunButton = (() => {
|
|
|
753
777
|
}
|
|
754
778
|
setAuthView("webauthn-username");
|
|
755
779
|
};
|
|
780
|
+
const handleWebauthnImport = async () => {
|
|
781
|
+
setError("");
|
|
782
|
+
setLoading(true);
|
|
783
|
+
try {
|
|
784
|
+
const username = formUsername.trim();
|
|
785
|
+
const recoveryCode = webauthnRecoverySeed.trim();
|
|
786
|
+
if (!username) {
|
|
787
|
+
throw new Error("Please enter your username");
|
|
788
|
+
}
|
|
789
|
+
if (!recoveryCode) {
|
|
790
|
+
throw new Error("Please enter your recovery code");
|
|
791
|
+
}
|
|
792
|
+
if (!isShogunCore(core)) {
|
|
793
|
+
throw new Error("WebAuthn recovery requires ShogunCore");
|
|
794
|
+
}
|
|
795
|
+
const result = await signUp("webauthn", username, {
|
|
796
|
+
seedPhrase: recoveryCode,
|
|
797
|
+
generateSeedPhrase: false,
|
|
798
|
+
});
|
|
799
|
+
if (!result || !result.success) {
|
|
800
|
+
throw new Error((result === null || result === void 0 ? void 0 : result.error) || "Failed to restore account");
|
|
801
|
+
}
|
|
802
|
+
const seedToDisplay = result.seedPhrase || recoveryCode;
|
|
803
|
+
setWebauthnSeedPhrase(seedToDisplay);
|
|
804
|
+
setWebauthnRecoverySeed("");
|
|
805
|
+
setShowCopySuccess(false);
|
|
806
|
+
setAuthView("webauthn-signup-result");
|
|
807
|
+
}
|
|
808
|
+
catch (e) {
|
|
809
|
+
setError(e.message || "Failed to restore WebAuthn account");
|
|
810
|
+
}
|
|
811
|
+
finally {
|
|
812
|
+
setLoading(false);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
756
815
|
const handleZkProofAuth = () => {
|
|
757
816
|
if (!hasPlugin("zkproof")) {
|
|
758
817
|
setError("ZK-Proof plugin not available");
|
|
@@ -791,8 +850,16 @@ export const ShogunButton = (() => {
|
|
|
791
850
|
if (!result || !result.success) {
|
|
792
851
|
throw new Error((result === null || result === void 0 ? void 0 : result.error) || "ZK-Proof signup failed");
|
|
793
852
|
}
|
|
794
|
-
|
|
795
|
-
|
|
853
|
+
const trapdoorValue = result.seedPhrase || result.trapdoor || "";
|
|
854
|
+
if (trapdoorValue) {
|
|
855
|
+
setZkSignupTrapdoor(trapdoorValue);
|
|
856
|
+
setShowZkTrapdoorCopySuccess(false);
|
|
857
|
+
setAuthView("zkproof-signup-result");
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
setAuthView("options");
|
|
861
|
+
setModalIsOpen(false);
|
|
862
|
+
}
|
|
796
863
|
}
|
|
797
864
|
catch (e) {
|
|
798
865
|
setError(e.message || "ZK-Proof signup failed");
|
|
@@ -911,7 +978,10 @@ export const ShogunButton = (() => {
|
|
|
911
978
|
setShowImportSuccess(false);
|
|
912
979
|
setRecoveredHint("");
|
|
913
980
|
setZkTrapdoor("");
|
|
981
|
+
setZkSignupTrapdoor("");
|
|
982
|
+
setShowZkTrapdoorCopySuccess(false);
|
|
914
983
|
setWebauthnSeedPhrase("");
|
|
984
|
+
setWebauthnRecoverySeed("");
|
|
915
985
|
};
|
|
916
986
|
const openModal = () => {
|
|
917
987
|
resetForm();
|
|
@@ -1037,7 +1107,52 @@ export const ShogunButton = (() => {
|
|
|
1037
1107
|
React.createElement("input", { type: "text", id: "username", value: formUsername, onChange: (e) => setFormUsername(e.target.value), disabled: loading, required: true, placeholder: "Enter your username", autoFocus: true })),
|
|
1038
1108
|
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: () => handleAuth("webauthn", formUsername), disabled: loading || !formUsername.trim() }, loading ? "Processing..." : `Continue with WebAuthn`),
|
|
1039
1109
|
React.createElement("div", { className: "shogun-form-footer" },
|
|
1040
|
-
React.createElement("button", { type: "button", className: "shogun-back-button", onClick: () => setAuthView("options"), disabled: loading }, "\u2190 Back to Options")
|
|
1110
|
+
React.createElement("button", { type: "button", className: "shogun-back-button", onClick: () => setAuthView("options"), disabled: loading }, "\u2190 Back to Options"),
|
|
1111
|
+
formMode === "login" && (React.createElement("button", { type: "button", className: "shogun-toggle-mode", onClick: () => setAuthView("webauthn-recovery"), disabled: loading }, "Restore with Recovery Code")))));
|
|
1112
|
+
const renderWebauthnRecoveryForm = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
1113
|
+
React.createElement("h3", null, "Restore WebAuthn Account"),
|
|
1114
|
+
React.createElement("div", { style: {
|
|
1115
|
+
backgroundColor: "#fef3c7",
|
|
1116
|
+
padding: "12px",
|
|
1117
|
+
borderRadius: "8px",
|
|
1118
|
+
marginBottom: "16px",
|
|
1119
|
+
border: "1px solid #f59e0b",
|
|
1120
|
+
} },
|
|
1121
|
+
React.createElement("p", { style: {
|
|
1122
|
+
fontSize: "14px",
|
|
1123
|
+
color: "#92400e",
|
|
1124
|
+
margin: "0",
|
|
1125
|
+
fontWeight: "500",
|
|
1126
|
+
} }, "\u26A0\uFE0F Recovery Required"),
|
|
1127
|
+
React.createElement("p", { style: {
|
|
1128
|
+
fontSize: "13px",
|
|
1129
|
+
color: "#a16207",
|
|
1130
|
+
margin: "4px 0 0 0",
|
|
1131
|
+
} }, "Enter the username and recovery code saved during signup to restore access on this device.")),
|
|
1132
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
1133
|
+
React.createElement("label", { htmlFor: "recoveryUsername" },
|
|
1134
|
+
React.createElement(UserIcon, null),
|
|
1135
|
+
React.createElement("span", null, "Username")),
|
|
1136
|
+
React.createElement("input", { type: "text", id: "recoveryUsername", value: formUsername, onChange: (e) => setFormUsername(e.target.value), disabled: loading, placeholder: "Enter your username", autoFocus: true })),
|
|
1137
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
1138
|
+
React.createElement("label", { htmlFor: "recoverySeed" },
|
|
1139
|
+
React.createElement(KeyIcon, null),
|
|
1140
|
+
React.createElement("span", null, "Recovery Code")),
|
|
1141
|
+
React.createElement("textarea", { id: "recoverySeed", value: webauthnRecoverySeed, onChange: (e) => setWebauthnRecoverySeed(e.target.value), disabled: loading, placeholder: "Enter your WebAuthn seed phrase...", rows: 4, style: {
|
|
1142
|
+
fontFamily: "monospace",
|
|
1143
|
+
fontSize: "12px",
|
|
1144
|
+
width: "100%",
|
|
1145
|
+
padding: "8px",
|
|
1146
|
+
border: "1px solid #f59e0b",
|
|
1147
|
+
borderRadius: "4px",
|
|
1148
|
+
backgroundColor: "#fffbeb",
|
|
1149
|
+
} })),
|
|
1150
|
+
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: handleWebauthnImport, disabled: loading }, loading ? "Restoring..." : "Restore Account"),
|
|
1151
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
1152
|
+
React.createElement("button", { type: "button", className: "shogun-back-button", onClick: () => {
|
|
1153
|
+
setError("");
|
|
1154
|
+
setAuthView("webauthn-username");
|
|
1155
|
+
}, disabled: loading }, "\u2190 Back to WebAuthn"))));
|
|
1041
1156
|
const renderRecoveryForm = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
1042
1157
|
React.createElement("div", { className: "shogun-form-group" },
|
|
1043
1158
|
React.createElement("label", { htmlFor: "username" },
|
|
@@ -1150,6 +1265,55 @@ export const ShogunButton = (() => {
|
|
|
1150
1265
|
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: handleZkProofLogin, disabled: loading || !zkTrapdoor.trim() }, loading ? "Processing..." : "Login Anonymously"),
|
|
1151
1266
|
React.createElement("div", { className: "shogun-form-footer" },
|
|
1152
1267
|
React.createElement("button", { className: "shogun-toggle-mode", onClick: () => setAuthView("options"), disabled: loading }, "Back to Login Options"))));
|
|
1268
|
+
const renderZkProofSignupResult = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
1269
|
+
React.createElement("h3", null, "ZK-Proof Account Created!"),
|
|
1270
|
+
React.createElement("div", { style: {
|
|
1271
|
+
backgroundColor: "#fef3c7",
|
|
1272
|
+
padding: "12px",
|
|
1273
|
+
borderRadius: "8px",
|
|
1274
|
+
marginBottom: "16px",
|
|
1275
|
+
border: "1px solid #f59e0b",
|
|
1276
|
+
} },
|
|
1277
|
+
React.createElement("p", { style: {
|
|
1278
|
+
fontSize: "14px",
|
|
1279
|
+
color: "#92400e",
|
|
1280
|
+
margin: "0",
|
|
1281
|
+
fontWeight: "500",
|
|
1282
|
+
} }, "\u26A0\uFE0F Important: Save Your Trapdoor"),
|
|
1283
|
+
React.createElement("p", { style: { fontSize: "13px", color: "#a16207", margin: "4px 0 0 0" } }, "This trapdoor lets you restore your anonymous identity on new devices. Store it securely and never share it.")),
|
|
1284
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
1285
|
+
React.createElement("label", null, "Your Trapdoor (Recovery Phrase):"),
|
|
1286
|
+
React.createElement("textarea", { value: zkSignupTrapdoor, readOnly: true, rows: 4, style: {
|
|
1287
|
+
fontFamily: "monospace",
|
|
1288
|
+
fontSize: "12px",
|
|
1289
|
+
width: "100%",
|
|
1290
|
+
padding: "8px",
|
|
1291
|
+
border: "2px solid #f59e0b",
|
|
1292
|
+
borderRadius: "4px",
|
|
1293
|
+
backgroundColor: "#fffbeb",
|
|
1294
|
+
} }),
|
|
1295
|
+
React.createElement("button", { type: "button", className: "shogun-submit-button", style: { marginTop: "8px" }, onClick: async () => {
|
|
1296
|
+
if (!zkSignupTrapdoor) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
try {
|
|
1300
|
+
if (navigator.clipboard) {
|
|
1301
|
+
await navigator.clipboard.writeText(zkSignupTrapdoor);
|
|
1302
|
+
setShowZkTrapdoorCopySuccess(true);
|
|
1303
|
+
setTimeout(() => setShowZkTrapdoorCopySuccess(false), 3000);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
catch (copyError) {
|
|
1307
|
+
console.warn("Failed to copy trapdoor:", copyError);
|
|
1308
|
+
}
|
|
1309
|
+
}, disabled: !zkSignupTrapdoor }, "Copy Trapdoor"),
|
|
1310
|
+
showZkTrapdoorCopySuccess && (React.createElement("p", { style: {
|
|
1311
|
+
color: "#047857",
|
|
1312
|
+
fontSize: "12px",
|
|
1313
|
+
marginTop: "6px",
|
|
1314
|
+
} }, "Trapdoor copied to clipboard!"))),
|
|
1315
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
1316
|
+
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: finalizeZkProofSignup }, "I Saved My Trapdoor"))));
|
|
1153
1317
|
const renderWebauthnSignupResult = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
1154
1318
|
React.createElement("h3", null, "WebAuthn Account Created!"),
|
|
1155
1319
|
React.createElement("div", { style: {
|
|
@@ -1292,9 +1456,13 @@ export const ShogunButton = (() => {
|
|
|
1292
1456
|
authView === "import" && renderImportForm(),
|
|
1293
1457
|
authView === "webauthn-username" &&
|
|
1294
1458
|
renderWebAuthnUsernameForm(),
|
|
1459
|
+
authView === "webauthn-recovery" &&
|
|
1460
|
+
renderWebauthnRecoveryForm(),
|
|
1295
1461
|
authView === "webauthn-signup-result" &&
|
|
1296
1462
|
renderWebauthnSignupResult(),
|
|
1297
|
-
authView === "zkproof-login" && renderZkProofLoginForm()
|
|
1463
|
+
authView === "zkproof-login" && renderZkProofLoginForm(),
|
|
1464
|
+
authView === "zkproof-signup-result" &&
|
|
1465
|
+
renderZkProofSignupResult()))))));
|
|
1298
1466
|
};
|
|
1299
1467
|
Button.displayName = "ShogunButton";
|
|
1300
1468
|
return Object.assign(Button, {
|