@multiplatform.one/gpg 6.0.0
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/LICENSE +201 -0
- package/dist/cjs/GpgButton.cjs +56 -0
- package/dist/cjs/GpgButton.native.js +78 -0
- package/dist/cjs/GpgButton.native.js.map +1 -0
- package/dist/cjs/GpgContext.cjs +37 -0
- package/dist/cjs/GpgContext.native.js +40 -0
- package/dist/cjs/GpgContext.native.js.map +1 -0
- package/dist/cjs/GpgProvider.cjs +112 -0
- package/dist/cjs/GpgProvider.native.js +127 -0
- package/dist/cjs/GpgProvider.native.js.map +1 -0
- package/dist/cjs/SignInWithGpg.cjs +132 -0
- package/dist/cjs/SignInWithGpg.native.js +159 -0
- package/dist/cjs/SignInWithGpg.native.js.map +1 -0
- package/dist/cjs/index.cjs +54 -0
- package/dist/cjs/index.native.js +57 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/screens.cjs +70 -0
- package/dist/cjs/screens.native.js +79 -0
- package/dist/cjs/screens.native.js.map +1 -0
- package/dist/cjs/useGpg.cjs +70 -0
- package/dist/cjs/useGpg.native.js +77 -0
- package/dist/cjs/useGpg.native.js.map +1 -0
- package/dist/esm/GpgButton.mjs +30 -0
- package/dist/esm/GpgButton.mjs.map +1 -0
- package/dist/esm/GpgButton.native.js +38 -0
- package/dist/esm/GpgButton.native.js.map +1 -0
- package/dist/esm/GpgContext.mjs +11 -0
- package/dist/esm/GpgContext.mjs.map +1 -0
- package/dist/esm/GpgContext.native.js +11 -0
- package/dist/esm/GpgContext.native.js.map +1 -0
- package/dist/esm/GpgProvider.mjs +86 -0
- package/dist/esm/GpgProvider.mjs.map +1 -0
- package/dist/esm/GpgProvider.native.js +98 -0
- package/dist/esm/GpgProvider.native.js.map +1 -0
- package/dist/esm/SignInWithGpg.mjs +107 -0
- package/dist/esm/SignInWithGpg.mjs.map +1 -0
- package/dist/esm/SignInWithGpg.native.js +120 -0
- package/dist/esm/SignInWithGpg.native.js.map +1 -0
- package/dist/esm/index.mjs +8 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +8 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/screens.mjs +32 -0
- package/dist/esm/screens.mjs.map +1 -0
- package/dist/esm/screens.native.js +38 -0
- package/dist/esm/screens.native.js.map +1 -0
- package/dist/esm/useGpg.mjs +45 -0
- package/dist/esm/useGpg.mjs.map +1 -0
- package/dist/esm/useGpg.native.js +49 -0
- package/dist/esm/useGpg.native.js.map +1 -0
- package/package.json +73 -0
- package/src/GpgButton.tsx +37 -0
- package/src/GpgContext.tsx +49 -0
- package/src/GpgProvider.tsx +126 -0
- package/src/SignInWithGpg.tsx +101 -0
- package/src/index.ts +14 -0
- package/src/screens.tsx +50 -0
- package/src/useGpg.ts +57 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useGpg } from "./useGpg.native.js";
|
|
2
|
+
import { GpgProvider, GPG_NONCE_QUERY_KEY } from "./GpgProvider.native.js";
|
|
3
|
+
import { GpgContext, GpgStatusState } from "./GpgContext.native.js";
|
|
4
|
+
import { default as default2 } from "./SignInWithGpg.native.js";
|
|
5
|
+
import { default as default3, GPGButton } from "./GpgButton.native.js";
|
|
6
|
+
import { gpgScreen, GpgScreenRegistrar, GpgBridge } from "./screens.native.js";
|
|
7
|
+
export { GPGButton, GPG_NONCE_QUERY_KEY, GpgBridge, GpgContext, GpgProvider, GpgScreenRegistrar, GpgStatusState, default2 as SignInWithGpg, default3 as SignInWithGpgButton, gpgScreen, useGpg };
|
|
8
|
+
//# sourceMappingURL=index.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useGpg","GpgProvider","GPG_NONCE_QUERY_KEY","GpgContext","GpgStatusState","default","default2","default3","GPGButton","gpgScreen","GpgScreenRegistrar","GpgBridge"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAKA,SAASA,MAAA,QAAc;AACvB,SAASC,WAAA,EAAaC,mBAAA,QAA2B;AACjD,SAASC,UAAA,EAAYC,cAAA,QAAsB;AAG3C,SAAoBC,OAAA,IAAXC,QAAA,QAAgC;AACzC,SAAoBD,OAAA,IAAXE,QAAA,EAAgCC,SAAA,QAAiB;AAE1D,SAASC,SAAA,EAAWC,kBAAA,EAAoBC,SAAA,QAAiB","ignoreList":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ModalStateProvider, routes, useRegisterScreens } from "@multiplatform.one/connectkit";
|
|
2
|
+
import { useContext as useReactContext } from "react";
|
|
3
|
+
import SignInWithGpg from "./SignInWithGpg.mjs";
|
|
4
|
+
import { GpgContext } from "./GpgContext.mjs";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
const gpgScreen = {
|
|
7
|
+
id: routes.SIGNINWITHGPG,
|
|
8
|
+
component: SignInWithGpg,
|
|
9
|
+
heading: "Sign in with GPG",
|
|
10
|
+
availableWhen: () => true,
|
|
11
|
+
showBack: false,
|
|
12
|
+
showInfo: false
|
|
13
|
+
};
|
|
14
|
+
const GpgScreenRegistrar = () => {
|
|
15
|
+
useRegisterScreens([gpgScreen]);
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
const GpgBridge = ({
|
|
19
|
+
children
|
|
20
|
+
}) => {
|
|
21
|
+
const signInWithGpg = useReactContext(GpgContext)?.enabled ?? true;
|
|
22
|
+
return /* @__PURE__ */jsxs(Fragment, {
|
|
23
|
+
children: [/* @__PURE__ */jsx(GpgScreenRegistrar, {}), /* @__PURE__ */jsx(ModalStateProvider, {
|
|
24
|
+
value: {
|
|
25
|
+
signInWithGpg
|
|
26
|
+
},
|
|
27
|
+
children
|
|
28
|
+
})]
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
export { GpgBridge, GpgScreenRegistrar, gpgScreen };
|
|
32
|
+
//# sourceMappingURL=screens.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ModalStateProvider","routes","useRegisterScreens","useContext","useReactContext","SignInWithGpg","GpgContext","Fragment","jsx","jsxs","gpgScreen","id","SIGNINWITHGPG","component","heading","availableWhen","showBack","showInfo","GpgScreenRegistrar","GpgBridge","children","signInWithGpg","enabled","value"],"sources":["../../src/screens.tsx"],"sourcesContent":[null],"mappings":"AACA,SACEA,kBAAA,EACAC,MAAA,EACAC,kBAAA,QAGK;AACP,SAASC,UAAA,IAAcC,eAAA,QAAuB;AAC9C,OAAOC,aAAA,MAAmB;AAC1B,SAASC,UAAA,QAAkB;AAkCvB,SAAAC,QAAA,EACEC,GAAA,EADFC,IAAA;AA9BG,MAAMC,SAAA,GAAuB;EAClCC,EAAA,EAAIV,MAAA,CAAOW,aAAA;EACXC,SAAA,EAAWR,aAAA;EACXS,OAAA,EAAS;EACTC,aAAA,EAAeA,CAAA,KAAM;EACrBC,QAAA,EAAU;EACVC,QAAA,EAAU;AACZ;AAOO,MAAMC,kBAAA,GAA+BA,CAAA,KAAM;EAChDhB,kBAAA,CAAmB,CAACQ,SAAS,CAAC;EAC9B,OAAO;AACT;AAQO,MAAMS,SAAA,GAA6EA,CAAC;EACzFC;AACF,MAAM;EACJ,MAAMC,aAAA,GAAgBjB,eAAA,CAAgBE,UAAU,GAAGgB,OAAA,IAAW;EAC9D,OACE,eAAAb,IAAA,CAAAF,QAAA;IACEa,QAAA,kBAAAZ,GAAA,CAACU,kBAAA,IAAmB,GACpB,eAAAV,GAAA,CAACR,kBAAA;MAAmBuB,KAAA,EAAO;QAAEF;MAAc;MAAID;IAAA,CAAS;EAAA,CAC1D;AAEJ","ignoreList":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ModalStateProvider, routes, useRegisterScreens } from "@multiplatform.one/connectkit";
|
|
4
|
+
import { useContext as useReactContext } from "react";
|
|
5
|
+
import SignInWithGpg from "./SignInWithGpg.native.js";
|
|
6
|
+
import { GpgContext } from "./GpgContext.native.js";
|
|
7
|
+
var gpgScreen = {
|
|
8
|
+
id: routes.SIGNINWITHGPG,
|
|
9
|
+
component: SignInWithGpg,
|
|
10
|
+
heading: "Sign in with GPG",
|
|
11
|
+
availableWhen: function availableWhen() {
|
|
12
|
+
return true;
|
|
13
|
+
},
|
|
14
|
+
showBack: false,
|
|
15
|
+
showInfo: false
|
|
16
|
+
};
|
|
17
|
+
var GpgScreenRegistrar = function GpgScreenRegistrar2() {
|
|
18
|
+
useRegisterScreens([gpgScreen]);
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
var GpgBridge = function GpgBridge2(param) {
|
|
22
|
+
var {
|
|
23
|
+
children
|
|
24
|
+
} = param;
|
|
25
|
+
var _ref;
|
|
26
|
+
var _useReactContext;
|
|
27
|
+
var signInWithGpg = (_ref = (_useReactContext = useReactContext(GpgContext)) === null || _useReactContext === void 0 ? void 0 : _useReactContext.enabled) !== null && _ref !== void 0 ? _ref : true;
|
|
28
|
+
return /* @__PURE__ */_jsxs(_Fragment, {
|
|
29
|
+
children: [/* @__PURE__ */_jsx(GpgScreenRegistrar, {}), /* @__PURE__ */_jsx(ModalStateProvider, {
|
|
30
|
+
value: {
|
|
31
|
+
signInWithGpg
|
|
32
|
+
},
|
|
33
|
+
children
|
|
34
|
+
})]
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
export { GpgBridge, GpgScreenRegistrar, gpgScreen };
|
|
38
|
+
//# sourceMappingURL=screens.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["jsx","_jsx","jsxs","_jsxs","Fragment","_Fragment","React","ModalStateProvider","routes","useRegisterScreens","useContext","useReactContext","SignInWithGpg","GpgContext","gpgScreen","id","SIGNINWITHGPG","component","heading","availableWhen","showBack","showInfo","GpgScreenRegistrar","GpgScreenRegistrar2","GpgBridge","GpgBridge2","param","children","_ref","_useReactContext","signInWithGpg","enabled"],"sources":["../../src/screens.tsx"],"sourcesContent":[null],"mappings":"AACA,SAAAA,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA,EAAAC,QAAA,IAAAC,SAAA;AAAA,OACEC,KAAA;AAAA,SACAC,kBAAA,EAAAC,MAAA,EAAAC,kBAAA;AAAA,SACAC,UAAA,IAAAC,eAAA;AAAA,OAGKC,aAAA;AACP,SAASC,UAAA,QAAc;AACvB,IAAAC,SAAO;EACPC,EAAA,EAAAP,MAAS,CAAAQ,aAAA;EAkCLC,SAAA,EAAAL,aAAA;EA9BGM,OAAM,oBAAuB;EAClCC,aAAW,WAAAA,cAAA;IACX,WAAW;EACX;EACAC,QAAA,OAAe;EACfC,QAAA,EAAU;AAAA;AAEZ,IAAAC,kBAAA,YAAAC,oBAAA;EAOOd,kBAAM,EACXK,SAAA,CACA;EACF;AAQO;AAAoF,IACzFU,SAAA,YAAAC,WAAAC,KAAA;EACF,IAAM;IAAAC;EAAA,IAAAD,KAAA;EACJ,IAAAE,IAAM;EACN,IAAAC,gBACE;EACE,IAAAC,aAAA,IAAAF,IAAC,IAAAC,gBAAmB,GAAAlB,eAAA,CAAAE,UAAA,eAAAgB,gBAAA,uBAAAA,gBAAA,CAAAE,OAAA,cAAAH,IAAA,cAAAA,IAAA;EAAA,OACpB,eAACzB,KAAA,CAAAE,SAAA;IAAuDsB,QAC1D,GAEJ,eAAA1B,IAAA,CAAAqB,kBAAA,O","ignoreList":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { GpgContext, GpgStatusState } from "./GpgContext.mjs";
|
|
3
|
+
const useGpg = () => {
|
|
4
|
+
const gpg = useContext(GpgContext);
|
|
5
|
+
if (!gpg) {
|
|
6
|
+
return {
|
|
7
|
+
message: null,
|
|
8
|
+
command: null,
|
|
9
|
+
status: GpgStatusState.ERROR,
|
|
10
|
+
error: new Error("useGpg hook must be inside a GpgProvider."),
|
|
11
|
+
isLoading: false,
|
|
12
|
+
isSuccess: false,
|
|
13
|
+
isError: true,
|
|
14
|
+
isReady: false,
|
|
15
|
+
signIn: () => Promise.reject(),
|
|
16
|
+
reset: () => {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const {
|
|
20
|
+
nonce,
|
|
21
|
+
message,
|
|
22
|
+
command,
|
|
23
|
+
status,
|
|
24
|
+
signIn,
|
|
25
|
+
resetStatus
|
|
26
|
+
} = gpg;
|
|
27
|
+
const isLoading = status === GpgStatusState.LOADING || nonce.isLoading;
|
|
28
|
+
const isSuccess = status === GpgStatusState.SUCCESS;
|
|
29
|
+
const isError = status === GpgStatusState.ERROR;
|
|
30
|
+
const isReady = !!message && !isLoading;
|
|
31
|
+
return {
|
|
32
|
+
message,
|
|
33
|
+
command,
|
|
34
|
+
status,
|
|
35
|
+
error: nonce.error,
|
|
36
|
+
isLoading,
|
|
37
|
+
isSuccess,
|
|
38
|
+
isError,
|
|
39
|
+
isReady,
|
|
40
|
+
signIn,
|
|
41
|
+
reset: () => resetStatus()
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export { useGpg };
|
|
45
|
+
//# sourceMappingURL=useGpg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useContext","GpgContext","GpgStatusState","useGpg","gpg","message","command","status","ERROR","error","Error","isLoading","isSuccess","isError","isReady","signIn","Promise","reject","reset","nonce","resetStatus","LOADING","SUCCESS"],"sources":["../../src/useGpg.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,UAAA,QAAkB;AAC3B,SAASC,UAAA,EAAYC,cAAA,QAAsB;AAoBpC,MAAMC,MAAA,GAASA,CAAA,KAAuB;EAC3C,MAAMC,GAAA,GAAMJ,UAAA,CAAWC,UAAU;EACjC,IAAI,CAACG,GAAA,EAAK;IACR,OAAO;MACLC,OAAA,EAAS;MACTC,OAAA,EAAS;MACTC,MAAA,EAAQL,cAAA,CAAeM,KAAA;MACvBC,KAAA,EAAO,IAAIC,KAAA,CAAM,2CAA2C;MAC5DC,SAAA,EAAW;MACXC,SAAA,EAAW;MACXC,OAAA,EAAS;MACTC,OAAA,EAAS;MACTC,MAAA,EAAQA,CAAA,KAAMC,OAAA,CAAQC,MAAA,CAAO;MAC7BC,KAAA,EAAOA,CAAA,KAAM,CAAC;IAChB;EACF;EAEA,MAAM;IAAEC,KAAA;IAAOd,OAAA;IAASC,OAAA;IAASC,MAAA;IAAQQ,MAAA;IAAQK;EAAY,IAAIhB,GAAA;EACjE,MAAMO,SAAA,GAAYJ,MAAA,KAAWL,cAAA,CAAemB,OAAA,IAAWF,KAAA,CAAMR,SAAA;EAC7D,MAAMC,SAAA,GAAYL,MAAA,KAAWL,cAAA,CAAeoB,OAAA;EAC5C,MAAMT,OAAA,GAAUN,MAAA,KAAWL,cAAA,CAAeM,KAAA;EAC1C,MAAMM,OAAA,GAAU,CAAC,CAACT,OAAA,IAAW,CAACM,SAAA;EAE9B,OAAO;IACLN,OAAA;IACAC,OAAA;IACAC,MAAA;IACAE,KAAA,EAAOU,KAAA,CAAMV,KAAA;IACbE,SAAA;IACAC,SAAA;IACAC,OAAA;IACAC,OAAA;IACAC,MAAA;IACAG,KAAA,EAAOA,CAAA,KAAME,WAAA,CAAY;EAC3B;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { GpgContext, GpgStatusState } from "./GpgContext.native.js";
|
|
3
|
+
var useGpg = function useGpg2() {
|
|
4
|
+
var gpg = useContext(GpgContext);
|
|
5
|
+
if (!gpg) {
|
|
6
|
+
return {
|
|
7
|
+
message: null,
|
|
8
|
+
command: null,
|
|
9
|
+
status: GpgStatusState.ERROR,
|
|
10
|
+
error: new Error("useGpg hook must be inside a GpgProvider."),
|
|
11
|
+
isLoading: false,
|
|
12
|
+
isSuccess: false,
|
|
13
|
+
isError: true,
|
|
14
|
+
isReady: false,
|
|
15
|
+
signIn: function signIn2() {
|
|
16
|
+
return Promise.reject();
|
|
17
|
+
},
|
|
18
|
+
reset: function reset() {}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
var {
|
|
22
|
+
nonce,
|
|
23
|
+
message,
|
|
24
|
+
command,
|
|
25
|
+
status,
|
|
26
|
+
signIn,
|
|
27
|
+
resetStatus
|
|
28
|
+
} = gpg;
|
|
29
|
+
var isLoading = status === GpgStatusState.LOADING || nonce.isLoading;
|
|
30
|
+
var isSuccess = status === GpgStatusState.SUCCESS;
|
|
31
|
+
var isError = status === GpgStatusState.ERROR;
|
|
32
|
+
var isReady = !!message && !isLoading;
|
|
33
|
+
return {
|
|
34
|
+
message,
|
|
35
|
+
command,
|
|
36
|
+
status,
|
|
37
|
+
error: nonce.error,
|
|
38
|
+
isLoading,
|
|
39
|
+
isSuccess,
|
|
40
|
+
isError,
|
|
41
|
+
isReady,
|
|
42
|
+
signIn,
|
|
43
|
+
reset: function reset() {
|
|
44
|
+
return resetStatus();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export { useGpg };
|
|
49
|
+
//# sourceMappingURL=useGpg.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useContext","GpgContext","GpgStatusState","useGpg","useGpg2","gpg","message","command","status","ERROR","error","Error","isLoading","isSuccess","isError","isReady","signIn","signIn2","Promise","reject","reset","nonce","resetStatus","LOADING","SUCCESS"],"sources":["../../src/useGpg.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,UAAA,QAAkB;AAC3B,SAASC,UAAA,EAAYC,cAAA,QAAsB;AAoBpC,IAAAC,MAAM,YAAgCC,QAAA;EAC3C,IAAAC,GAAM,GAAAL,UAAM,CAAAC,UAAW;EACvB,IAAI,CAACI,GAAA,EAAK;IACR,OAAO;MACLC,OAAA,EAAS;MACTC,OAAA,EAAS;MACTC,MAAA,EAAQN,cAAA,CAAeO,KAAA;MACvBC,KAAA,EAAO,IAAIC,KAAA,CAAM,2CAA2C;MAC5DC,SAAA,EAAW;MACXC,SAAA,EAAW;MACXC,OAAA,EAAS;MACTC,OAAA,EAAS;MACTC,MAAA,EAAQ,SAAMC,OAAQA,CAAA;QACtB,OAAOC,OAAM,CAAAC,MAAA;MAAC;MAChBC,KAAA,WAAAA,MAAA,GACF;IAEA;EACA;EACA;IAAMC,KAAA;IAAAf,OAAY;IAAAC,OAAW;IAAAC,MAAA;IAAAQ,MAAe;IAAAM;EAAA,IAAAjB,GAAA;EAC5C,IAAAO,SAAM,GAAUJ,MAAA,KAAWN,cAAA,CAAeqB,OAAA,IAAAF,KAAA,CAAAT,SAAA;EAC1C,IAAAC,SAAM,GAAUL,MAAE,KAAAN,cAAY,CAAAsB,OAAA;EAE9B,IAAAV,OAAO,GAAAN,MAAA,KAAAN,cAAA,CAAAO,KAAA;EAAA,IACLM,OAAA,KAAAT,OAAA,KAAAM,SAAA;EAAA,OACA;IACAN,OAAA;IACAC,OAAO;IACPC,MAAA;IACAE,KAAA,EAAAW,KAAA,CAAAX,KAAA;IACAE,SAAA;IACAC,SAAA;IACAC,OAAA;IACAC,OAAO;IACTC,MAAA;IACFI,KAAA,WAAAA,MAAA","ignoreList":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@multiplatform.one/gpg",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Sign-In With GPG (SIWG) screen + GpgProvider for the connectkit modal — no viem",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"connectkit",
|
|
7
|
+
"cross-platform",
|
|
8
|
+
"gpg",
|
|
9
|
+
"multiplatform",
|
|
10
|
+
"react",
|
|
11
|
+
"react-native",
|
|
12
|
+
"sign-in",
|
|
13
|
+
"siwg",
|
|
14
|
+
"tamagui"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://multiplatform.one",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one/issues",
|
|
19
|
+
"email": "support@risserlabs.com"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"author": "BitSpur <support@risserlabs.com> (https://risserlabs.com)",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one.git"
|
|
26
|
+
},
|
|
27
|
+
"source": "src/index.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src",
|
|
31
|
+
"types"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"main": "src/index.ts",
|
|
35
|
+
"module": "src/index.ts",
|
|
36
|
+
"types": "src/index.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
"./package.json": "./package.json",
|
|
39
|
+
".": {
|
|
40
|
+
"source": "./src/index.ts",
|
|
41
|
+
"react-native": {
|
|
42
|
+
"types": "./src/index.ts",
|
|
43
|
+
"import": "./src/index.ts",
|
|
44
|
+
"require": "./src/index.ts",
|
|
45
|
+
"default": "./src/index.ts"
|
|
46
|
+
},
|
|
47
|
+
"types": "./src/index.ts",
|
|
48
|
+
"import": "./dist/esm/index.mjs",
|
|
49
|
+
"require": "./dist/cjs/index.cjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@multiplatform.one/connectkit": "6.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@tamagui/build": "2.0.0-rc.41",
|
|
60
|
+
"@types/react": "~19.2.14",
|
|
61
|
+
"react": "19.2.5",
|
|
62
|
+
"typescript": "~5.9.3",
|
|
63
|
+
"@multiplatform.one/config": "6.0.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@tanstack/react-query": "^5.62.0",
|
|
67
|
+
"react": "^19.1.0"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"build": "rm -rf dist types 2>/dev/null && tamagui-build --ts-project ./tsconfig.build.json && node scripts/fix-dist-specifiers.mjs"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button, ResetContainer, useModal } from "@multiplatform.one/connectkit";
|
|
3
|
+
|
|
4
|
+
// Standalone trigger for the GPG flow. GPG is NOT a wallet connector — there is nothing
|
|
5
|
+
// to connect first — so this button simply opens the SAME modal straight to the GPG
|
|
6
|
+
// screen (no connect step), mirroring how the SIWE button opens its screen.
|
|
7
|
+
type ButtonProps = {
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const GPGButton: React.FC<ButtonProps> = ({ children }) => {
|
|
12
|
+
// GPG is connection-free: open the SAME modal straight to its screen via the
|
|
13
|
+
// generic registry opener (gpg registers the 'signInWithGpg' screen).
|
|
14
|
+
const { openScreen } = useModal();
|
|
15
|
+
return (
|
|
16
|
+
<Button
|
|
17
|
+
key="button"
|
|
18
|
+
style={{ margin: 0 }}
|
|
19
|
+
arrow
|
|
20
|
+
// preventDefault so we never submit a surrounding host <form> (e.g. keycloak's
|
|
21
|
+
// login form) — the underlying styled button can render as type=submit.
|
|
22
|
+
onClick={(e?: { preventDefault?: () => void }) => {
|
|
23
|
+
e?.preventDefault?.();
|
|
24
|
+
openScreen("signInWithGpg");
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
{children ?? "Sign in with GPG"}
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const GPGButtonComponent: React.FC<ButtonProps> = ({ ...props }) => (
|
|
33
|
+
<ResetContainer>
|
|
34
|
+
<GPGButton {...props} />
|
|
35
|
+
</ResetContainer>
|
|
36
|
+
);
|
|
37
|
+
export default GPGButtonComponent;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
// Sign-In With GPG (SIWG) is a SEPARATE, connection-free signer: unlike a wallet
|
|
5
|
+
// there is nothing to "connect" first — the user signs a challenge in their terminal
|
|
6
|
+
// and pastes the result back. This module deliberately pulls in NO wagmi/viem, so it
|
|
7
|
+
// can later move into a standalone `@multiplatform.one/gpg` package on top of connectkit.
|
|
8
|
+
|
|
9
|
+
export enum GpgStatusState {
|
|
10
|
+
READY = "ready",
|
|
11
|
+
LOADING = "loading",
|
|
12
|
+
SUCCESS = "success",
|
|
13
|
+
ERROR = "error",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type GpgConfig = {
|
|
17
|
+
// Required: fetch a fresh nonce from the backend (e.g. keycloak /realms/<realm>/siww/nonce).
|
|
18
|
+
getNonce: () => Promise<string>;
|
|
19
|
+
// Required: submit the signed credential. The screen produces
|
|
20
|
+
// username = base64(armoredSignature + armoredPublicKey)
|
|
21
|
+
// password = base64(message)
|
|
22
|
+
// For a keycloak browser login this writes the login form and submits it (the page
|
|
23
|
+
// then navigates); an API consumer may POST and return a boolean instead.
|
|
24
|
+
onSign: (cred: {
|
|
25
|
+
username: string;
|
|
26
|
+
password: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}) => Promise<boolean | void> | boolean | void;
|
|
29
|
+
|
|
30
|
+
// Optional: override the challenge message (default = the standard SIWG message).
|
|
31
|
+
buildMessage?: (args: { nonce: string }) => string;
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
nonceRefetchInterval?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type GpgContextValue = Required<
|
|
37
|
+
Pick<GpgConfig, "getNonce" | "onSign" | "enabled" | "nonceRefetchInterval">
|
|
38
|
+
> & {
|
|
39
|
+
buildMessage?: GpgConfig["buildMessage"];
|
|
40
|
+
nonce: ReturnType<typeof useQuery<string | null>>;
|
|
41
|
+
// Derived from the nonce — what the user copies/signs, and the ready-to-run command.
|
|
42
|
+
message: string | null;
|
|
43
|
+
command: string | null;
|
|
44
|
+
status: GpgStatusState;
|
|
45
|
+
signIn: (pastedBlob: string) => Promise<boolean>;
|
|
46
|
+
resetStatus: () => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const GpgContext = createContext<GpgContextValue | null>(null);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ReactNode, useContext, useMemo, useState } from "react";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
import { Context as ConnectKitContext } from "@multiplatform.one/connectkit";
|
|
5
|
+
import { GpgContext, GpgConfig, GpgStatusState } from "./GpgContext";
|
|
6
|
+
|
|
7
|
+
type Props = GpgConfig & {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
onSignIn?: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const GPG_NONCE_QUERY_KEY = "ckGpgNonce";
|
|
13
|
+
|
|
14
|
+
// UTF-8-safe base64 — the server decodes with new String(decoder.decode(...), UTF_8),
|
|
15
|
+
// so plain btoa would corrupt non-ASCII. Must keep the unescape(encodeURIComponent()).
|
|
16
|
+
const b64 = (s: string) => window.btoa(unescape(encodeURIComponent(s)));
|
|
17
|
+
|
|
18
|
+
// The default SIWG challenge. Only the header line (domain) and the Nonce line are
|
|
19
|
+
// load-bearing per the server's GpgMessage grammar; the rest is a cosmetic envelope.
|
|
20
|
+
function defaultBuildMessage({ nonce }: { nonce: string }): string {
|
|
21
|
+
return (
|
|
22
|
+
`${window.location.host} wants you to sign in with your GPG key.\n\n` +
|
|
23
|
+
`URI: ${window.location.origin}\n` +
|
|
24
|
+
`Version: 1\n` +
|
|
25
|
+
`Nonce: ${nonce}\n` +
|
|
26
|
+
`Issued At: ${new Date().toISOString()}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const GpgProvider = ({
|
|
31
|
+
children,
|
|
32
|
+
enabled = true,
|
|
33
|
+
nonceRefetchInterval = 1000 * 60 * 5,
|
|
34
|
+
buildMessage,
|
|
35
|
+
onSignIn,
|
|
36
|
+
...gpgConfig
|
|
37
|
+
}: Props) => {
|
|
38
|
+
const [status, setStatus] = useState<GpgStatusState>(GpgStatusState.READY);
|
|
39
|
+
const resetStatus = () => setStatus(GpgStatusState.READY);
|
|
40
|
+
|
|
41
|
+
// Mirror SIWEProvider's guards: single instance, and mounted OUTSIDE ConnectKitProvider
|
|
42
|
+
// so the modal's GPG screen can read this context.
|
|
43
|
+
if (useContext(GpgContext)) {
|
|
44
|
+
throw new Error("Multiple, nested usages of GpgProvider detected. Please use only one.");
|
|
45
|
+
}
|
|
46
|
+
if (useContext(ConnectKitContext)) {
|
|
47
|
+
throw new Error("ConnectKitProvider must be mounted inside GpgProvider.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const nonce = useQuery({
|
|
51
|
+
queryKey: [GPG_NONCE_QUERY_KEY],
|
|
52
|
+
queryFn: () => gpgConfig.getNonce(),
|
|
53
|
+
refetchInterval: nonceRefetchInterval,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Compute the challenge ONCE per nonce (it embeds an Issued At timestamp); recomputing
|
|
57
|
+
// on every render would desync the displayed/copied message from the one base64'd into
|
|
58
|
+
// `password`, breaking the signature (which is verified over the exact message bytes).
|
|
59
|
+
const message = useMemo(
|
|
60
|
+
() => (nonce.data ? (buildMessage ?? defaultBuildMessage)({ nonce: nonce.data }) : null),
|
|
61
|
+
[nonce.data, buildMessage],
|
|
62
|
+
);
|
|
63
|
+
// The ready-to-run terminal command: detached armored signature + the exported public
|
|
64
|
+
// key, concatenated — exactly the blob the user pastes back. printf %s (no newline) so
|
|
65
|
+
// the signed bytes are byte-identical to `message`.
|
|
66
|
+
const command = useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
message
|
|
69
|
+
? `{ printf %s '${message}' | gpg --detach-sign --armor; gpg --armor --export; }`
|
|
70
|
+
: null,
|
|
71
|
+
[message],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const signIn = async (pastedBlob: string): Promise<boolean> => {
|
|
75
|
+
const blob = pastedBlob.trim();
|
|
76
|
+
if (
|
|
77
|
+
!blob.includes("-----BEGIN PGP SIGNATURE-----") ||
|
|
78
|
+
!blob.includes("-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
|
79
|
+
) {
|
|
80
|
+
setStatus(GpgStatusState.ERROR);
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Paste the FULL output — it must contain both a PGP SIGNATURE and a PUBLIC KEY block.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (!message) {
|
|
86
|
+
setStatus(GpgStatusState.ERROR);
|
|
87
|
+
throw new Error("Could not fetch nonce");
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
setStatus(GpgStatusState.LOADING);
|
|
91
|
+
// username = base64(signature + public key), password = base64(message).
|
|
92
|
+
const result = await gpgConfig.onSign({
|
|
93
|
+
username: b64(blob),
|
|
94
|
+
password: b64(message),
|
|
95
|
+
message,
|
|
96
|
+
});
|
|
97
|
+
// A keycloak browser login navigates away here; an API consumer returns a boolean.
|
|
98
|
+
if (result === false) throw new Error("GPG sign-in was rejected");
|
|
99
|
+
setStatus(GpgStatusState.SUCCESS);
|
|
100
|
+
onSignIn?.();
|
|
101
|
+
return true;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
setStatus(GpgStatusState.ERROR);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<GpgContext.Provider
|
|
110
|
+
value={{
|
|
111
|
+
enabled,
|
|
112
|
+
nonceRefetchInterval,
|
|
113
|
+
buildMessage,
|
|
114
|
+
...gpgConfig,
|
|
115
|
+
nonce,
|
|
116
|
+
message,
|
|
117
|
+
command,
|
|
118
|
+
status,
|
|
119
|
+
signIn,
|
|
120
|
+
resetStatus,
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
{children}
|
|
124
|
+
</GpgContext.Provider>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React, { CSSProperties, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useContext,
|
|
5
|
+
PageContent,
|
|
6
|
+
ModalBody,
|
|
7
|
+
ModalContent,
|
|
8
|
+
Button,
|
|
9
|
+
CopyToClipboard,
|
|
10
|
+
} from "@multiplatform.one/connectkit";
|
|
11
|
+
import { useGpg } from "./useGpg";
|
|
12
|
+
|
|
13
|
+
// Sign-In With GPG screen: a connection-free, copy/paste flow rendered inside the SAME
|
|
14
|
+
// ConnectKit modal as the wallet screens. Reads everything from useGpg() (the consumer's
|
|
15
|
+
// GpgProvider supplies the backend-specific getNonce/onSign) — no keycloak knowledge here.
|
|
16
|
+
const textarea: CSSProperties = {
|
|
17
|
+
width: "100%",
|
|
18
|
+
boxSizing: "border-box",
|
|
19
|
+
fontFamily: "var(--ck-font-family, monospace)",
|
|
20
|
+
fontSize: 11,
|
|
21
|
+
lineHeight: 1.4,
|
|
22
|
+
padding: 8,
|
|
23
|
+
borderRadius: "var(--ck-primary-button-border-radius, 12px)",
|
|
24
|
+
border: "1px solid var(--ck-body-divider, #d3d3d3)",
|
|
25
|
+
background: "var(--ck-body-background-secondary, #f6f7f9)",
|
|
26
|
+
color: "var(--ck-body-color, inherit)",
|
|
27
|
+
resize: "vertical",
|
|
28
|
+
};
|
|
29
|
+
const label: CSSProperties = {
|
|
30
|
+
textAlign: "left",
|
|
31
|
+
fontSize: 14,
|
|
32
|
+
fontWeight: 500,
|
|
33
|
+
margin: "4px 0",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const SignInWithGpg: React.FC = () => {
|
|
37
|
+
const context = useContext();
|
|
38
|
+
const { message, command, signIn, isLoading, isReady } = useGpg();
|
|
39
|
+
const [pasted, setPasted] = useState("");
|
|
40
|
+
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
const [busy, setBusy] = useState(false);
|
|
42
|
+
|
|
43
|
+
const onSubmit = async () => {
|
|
44
|
+
setError(null);
|
|
45
|
+
setBusy(true);
|
|
46
|
+
try {
|
|
47
|
+
await signIn(pasted);
|
|
48
|
+
// A keycloak browser login navigates away; for API consumers, close the modal.
|
|
49
|
+
setTimeout(() => context.setOpen(false), 1000);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
setError(e instanceof Error ? e.message : "GPG sign-in failed");
|
|
52
|
+
setBusy(false);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<PageContent style={{ width: 320 }}>
|
|
58
|
+
<ModalContent style={{ paddingBottom: 8, gap: 6 }}>
|
|
59
|
+
<ModalBody style={label}>1. Sign the challenge in your terminal:</ModalBody>
|
|
60
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
|
61
|
+
<CopyToClipboard variant="button" string={command ?? ""}>
|
|
62
|
+
Copy gpg command
|
|
63
|
+
</CopyToClipboard>
|
|
64
|
+
<CopyToClipboard variant="button" string={message ?? ""}>
|
|
65
|
+
Copy message only
|
|
66
|
+
</CopyToClipboard>
|
|
67
|
+
</div>
|
|
68
|
+
<textarea
|
|
69
|
+
readOnly
|
|
70
|
+
rows={4}
|
|
71
|
+
value={message ?? (isLoading ? "Loading challenge…" : "")}
|
|
72
|
+
style={textarea}
|
|
73
|
+
/>
|
|
74
|
+
<ModalBody style={label}>2. Paste the full output below:</ModalBody>
|
|
75
|
+
<textarea
|
|
76
|
+
rows={6}
|
|
77
|
+
value={pasted}
|
|
78
|
+
onChange={(e) => setPasted(e.target.value)}
|
|
79
|
+
placeholder="-----BEGIN PGP SIGNATURE----- … and your -----BEGIN PGP PUBLIC KEY BLOCK-----"
|
|
80
|
+
style={textarea}
|
|
81
|
+
/>
|
|
82
|
+
<Button onClick={onSubmit} disabled={!isReady || !pasted || busy} waiting={busy} arrow>
|
|
83
|
+
Sign in
|
|
84
|
+
</Button>
|
|
85
|
+
{error && (
|
|
86
|
+
<ModalBody
|
|
87
|
+
style={{
|
|
88
|
+
color: "var(--ck-body-color-danger, #c9190b)",
|
|
89
|
+
fontSize: 13,
|
|
90
|
+
textAlign: "left",
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{error}
|
|
94
|
+
</ModalBody>
|
|
95
|
+
)}
|
|
96
|
+
</ModalContent>
|
|
97
|
+
</PageContent>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default SignInWithGpg;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @multiplatform.one/gpg — Sign-In With GPG (SIWG) for the connectkit modal.
|
|
2
|
+
//
|
|
3
|
+
// A connection-free signer: the user signs a challenge in their terminal and pastes
|
|
4
|
+
// the result back. Pulls in NO wagmi/viem — depends only on @multiplatform.one/connectkit
|
|
5
|
+
// — so a GPG-only app can install it without the wallet stack.
|
|
6
|
+
export { useGpg } from "./useGpg";
|
|
7
|
+
export { GpgProvider, GPG_NONCE_QUERY_KEY } from "./GpgProvider";
|
|
8
|
+
export { GpgContext, GpgStatusState } from "./GpgContext";
|
|
9
|
+
export type { GpgConfig, GpgContextValue } from "./GpgContext";
|
|
10
|
+
|
|
11
|
+
export { default as SignInWithGpg } from "./SignInWithGpg";
|
|
12
|
+
export { default as SignInWithGpgButton, GPGButton } from "./GpgButton";
|
|
13
|
+
|
|
14
|
+
export { gpgScreen, GpgScreenRegistrar, GpgBridge } from "./screens";
|
package/src/screens.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ModalStateProvider,
|
|
4
|
+
routes,
|
|
5
|
+
useRegisterScreens,
|
|
6
|
+
type ConnectCallbackProps,
|
|
7
|
+
type ScreenDef,
|
|
8
|
+
} from "@multiplatform.one/connectkit";
|
|
9
|
+
import { useContext as useReactContext } from "react";
|
|
10
|
+
import SignInWithGpg from "./SignInWithGpg";
|
|
11
|
+
import { GpgContext } from "./GpgContext";
|
|
12
|
+
|
|
13
|
+
// The GPG screen contributed to connectkit's signer-agnostic modal. GPG has no
|
|
14
|
+
// connect step, so it's available in both connected and disconnected states.
|
|
15
|
+
export const gpgScreen: ScreenDef = {
|
|
16
|
+
id: routes.SIGNINWITHGPG,
|
|
17
|
+
component: SignInWithGpg,
|
|
18
|
+
heading: "Sign in with GPG",
|
|
19
|
+
availableWhen: () => true,
|
|
20
|
+
showBack: false,
|
|
21
|
+
showInfo: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Mount inside ConnectKitProvider (under ScreenRegistryProvider) to publish the GPG
|
|
26
|
+
* screen to the modal. Renders nothing. web3 mounts this alongside its wallet
|
|
27
|
+
* screens; a gpg-only app uses {@link GpgBridge}.
|
|
28
|
+
*/
|
|
29
|
+
export const GpgScreenRegistrar: React.FC = () => {
|
|
30
|
+
useRegisterScreens([gpgScreen]);
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The signer bridge for a GPG-only app (no wagmi/web3). Pass to ConnectKitProvider's
|
|
36
|
+
* `bridge` prop: it registers the GPG screen and injects a ModalState advertising
|
|
37
|
+
* GPG sign-in is available. Open the screen via the SignInWithGpgButton (which calls
|
|
38
|
+
* `openScreen('signInWithGpg')`).
|
|
39
|
+
*/
|
|
40
|
+
export const GpgBridge: React.FC<{ children?: React.ReactNode } & ConnectCallbackProps> = ({
|
|
41
|
+
children,
|
|
42
|
+
}) => {
|
|
43
|
+
const signInWithGpg = useReactContext(GpgContext)?.enabled ?? true;
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<GpgScreenRegistrar />
|
|
47
|
+
<ModalStateProvider value={{ signInWithGpg }}>{children}</ModalStateProvider>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
};
|