diff --git a/package.json b/package.json
index 87f75fe..247c4ab 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
+ "@void-cat/api": "^1.0.7",
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
"buffer": "^6.0.3",
"emoji-mart": "^5.5.2",
diff --git a/public/icons.svg b/public/icons.svg
index d8a86b1..94c70f7 100644
--- a/public/icons.svg
+++ b/public/icons.svg
@@ -30,5 +30,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/element/chat-message.tsx b/src/element/chat-message.tsx
index 698d3f5..1a46e4f 100644
--- a/src/element/chat-message.tsx
+++ b/src/element/chat-message.tsx
@@ -1,5 +1,5 @@
import { useUserProfile } from "@snort/system-react";
-import { NostrEvent, parseZap, EventPublisher, EventKind } from "@snort/system";
+import { NostrEvent, parseZap, EventKind } from "@snort/system";
import { useRef, useState, useMemo } from "react";
import {
useMediaQuery,
@@ -18,6 +18,7 @@ import { Text } from "./text";
import { SendZapsDialog } from "./send-zap";
import { findTag } from "../utils";
import type { EmojiPack } from "../hooks/emoji";
+import { useLogin } from "../hooks/login";
interface Emoji {
id: string;
@@ -54,6 +55,7 @@ export function ChatMessage({
const isHovering = useHover(ref);
const [showZapDialog, setShowZapDialog] = useState(false);
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
+ const login = useLogin();
const profile = useUserProfile(
System,
inView?.isIntersecting ? ev.pubkey : undefined
@@ -97,7 +99,7 @@ export function ChatMessage({
setShowZapDialog(false);
let reply = null;
try {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (emoji.native) {
reply = await pub?.react(ev, emoji.native || "+1");
} else {
@@ -117,7 +119,7 @@ export function ChatMessage({
console.debug(reply);
System.BroadcastEvent(reply);
}
- } catch (error) {}
+ } catch (error) { }
}
// @ts-expect-error
@@ -176,16 +178,16 @@ export function ChatMessage({
style={
isTablet
? {
- display: showZapDialog || isHovering ? "flex" : "none",
- }
+ display: showZapDialog || isHovering ? "flex" : "none",
+ }
: {
- position: "fixed",
- top: topOffset - 12,
- left: leftOffset - 32,
- opacity: showZapDialog || isHovering ? 1 : 0,
- pointerEvents:
- showZapDialog || isHovering ? "auto" : "none",
- }
+ position: "fixed",
+ top: topOffset - 12,
+ left: leftOffset - 32,
+ opacity: showZapDialog || isHovering ? 1 : 0,
+ pointerEvents:
+ showZapDialog || isHovering ? "auto" : "none",
+ }
}
>
{zapTarget && (
diff --git a/src/element/copy.css b/src/element/copy.css
new file mode 100644
index 0000000..be437e1
--- /dev/null
+++ b/src/element/copy.css
@@ -0,0 +1,11 @@
+.copy {
+ display: flex;
+ cursor: pointer;
+ align-items: center;
+ gap: 8px;
+}
+
+.copy .body {
+ font-size: small;
+ color: white;
+}
\ No newline at end of file
diff --git a/src/element/copy.tsx b/src/element/copy.tsx
new file mode 100644
index 0000000..2ccc0ba
--- /dev/null
+++ b/src/element/copy.tsx
@@ -0,0 +1,23 @@
+import "./copy.css";
+import { useCopy } from "hooks/copy";
+import { Icon } from "./icon";
+
+export interface CopyProps {
+ text: string;
+ maxSize?: number;
+ className?: string;
+}
+export default function Copy({ text, maxSize = 32, className }: CopyProps) {
+ const { copy, copied } = useCopy();
+ const sliceLength = maxSize / 2;
+ const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` : text;
+
+ return (
+
copy(text)}>
+ {trimmed}
+
+ {copied ? : }
+
+
+ );
+}
diff --git a/src/element/follow-button.tsx b/src/element/follow-button.tsx
index afe6f93..8d34fce 100644
--- a/src/element/follow-button.tsx
+++ b/src/element/follow-button.tsx
@@ -1,4 +1,4 @@
-import { EventKind, EventPublisher } from "@snort/system";
+import { EventKind } from "@snort/system";
import { useLogin } from "hooks/login";
import useFollows from "hooks/follows";
import AsyncButton from "element/async-button";
@@ -12,10 +12,11 @@ export function LoggedInFollowButton({
pubkey: string;
}) {
const { contacts, relays } = useFollows(loggedIn, true);
+ const login = useLogin();
const isFollowing = contacts.find((t) => t.at(1) === pubkey);
async function unfollow() {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (pub) {
const ev = await pub.generic((eb) => {
eb.kind(EventKind.ContactList).content(JSON.stringify(relays));
@@ -32,7 +33,7 @@ export function LoggedInFollowButton({
}
async function follow() {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (pub) {
const ev = await pub.generic((eb) => {
eb.kind(EventKind.ContactList).content(JSON.stringify(relays));
diff --git a/src/element/login-signup.css b/src/element/login-signup.css
new file mode 100644
index 0000000..39a199f
--- /dev/null
+++ b/src/element/login-signup.css
@@ -0,0 +1,13 @@
+.avatar-input {
+ width: 90px;
+ height: 90px;
+ background-color: #aaa;
+ border-radius: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ background-image: var(--img);
+ background-position: center;
+ background-size: cover;
+}
\ No newline at end of file
diff --git a/src/element/login-signup.tsx b/src/element/login-signup.tsx
new file mode 100644
index 0000000..e55a93d
--- /dev/null
+++ b/src/element/login-signup.tsx
@@ -0,0 +1,138 @@
+import "./login-signup.css";
+import { CSSProperties, useState } from "react";
+import { EventPublisher, UserMetadata } from "@snort/system";
+import { schnorr } from "@noble/curves/secp256k1";
+import { bytesToHex } from "@noble/curves/abstract/utils";
+
+import AsyncButton from "./async-button";
+import { Login, System } from "index";
+import { Icon } from "./icon";
+import Copy from "./copy";
+import { hexToBech32, openFile } from "utils";
+import { VoidApi } from "@void-cat/api";
+import { upload } from "@testing-library/user-event/dist/upload";
+import { LoginType } from "login";
+
+enum Stage {
+ Login = 0,
+ Details = 1,
+ SaveKey = 2
+}
+
+export function LoginSignup({ close }: { close: () => void }) {
+ const [error, setError] = useState("");
+ const [stage, setStage] = useState(Stage.Login);
+ const [username, setUsername] = useState("");
+ const [avatar, setAvatar] = useState("");
+ const [key, setNewKey] = useState("");
+
+ async function doLogin() {
+ try {
+ const pub = await EventPublisher.nip7();
+ if (pub) {
+ Login.loginWithPubkey(pub.pubKey, LoginType.Nip7);
+ }
+ } catch (e) {
+ console.error(e);
+ if (e instanceof Error) {
+ setError(e.message);
+ } else {
+ setError(e as string);
+ }
+ }
+ }
+
+ function createAccount() {
+ const newKey = bytesToHex(schnorr.utils.randomPrivateKey());
+ setNewKey(newKey);
+ setStage(Stage.Details);
+ }
+
+ function loginWithKey() {
+ Login.loginWithPrivateKey(key);
+ close();
+ }
+
+ async function uploadAvatar() {
+ const file = await openFile();
+ if (file) {
+ const VoidCatHost = "https://void.cat"
+ const api = new VoidApi(VoidCatHost);
+ const uploader = api.getUploader(file);
+ const result = await uploader.upload({
+ "V-Strip-Metadata": "true"
+ })
+ if (result.ok) {
+ const resultUrl = result.file?.metadata?.url ?? `${VoidCatHost}/d/${result.file?.id}`;
+ setAvatar(resultUrl);
+ } else {
+ setError(result.errorMessage ?? "Upload failed");
+ }
+ }
+ }
+
+ async function saveProfile() {
+ const profile = {
+ name: username,
+ picture: avatar
+ } as UserMetadata;
+
+ const pub = EventPublisher.privateKey(key);
+ const ev = await pub.metadata(profile);
+ console.debug(ev);
+ System.BroadcastEvent(ev);
+
+ setStage(Stage.SaveKey);
+ }
+
+ switch (stage) {
+ case Stage.Login: {
+ return <>
+ Login
+ {"nostr" in window &&
+
+ Nostr Extension
+ }
+
+ {error && {error}}
+ >
+ }
+ case Stage.Details: {
+ return <>
+ Setup Profile
+
+
+
+ setUsername(e.target.value)} />
+
+
You can change this later
+
+
+ Save
+
+ >
+ }
+ case Stage.SaveKey: {
+ return <>
+ Save Key
+
+ Nostr uses private keys, please save yours, if you lose this key you wont be able to login to your account anymore!
+
+
+
+
+
+ >
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/element/new-goal.tsx b/src/element/new-goal.tsx
index 898a7ae..a2c9804 100644
--- a/src/element/new-goal.tsx
+++ b/src/element/new-goal.tsx
@@ -2,14 +2,12 @@ import "./new-goal.css";
import * as Dialog from "@radix-ui/react-dialog";
import AsyncButton from "./async-button";
-import { NostrLink, EventPublisher } from "@snort/system";
-import { unixNow } from "@snort/shared";
+import { NostrLink } from "@snort/system";
import { Icon } from "element/icon";
-import { useEffect, useState } from "react";
-import { eventLink } from "utils";
-import { NostrProviderDialog } from "./nostr-provider-dialog";
+import { useState } from "react";
import { System } from "index";
import { GOAL } from "const";
+import { useLogin } from "hooks/login";
interface NewGoalDialogProps {
link: NostrLink;
@@ -17,12 +15,13 @@ interface NewGoalDialogProps {
export function NewGoalDialog({ link }: NewGoalDialogProps) {
const [open, setOpen] = useState(false);
+ const login = useLogin();
const [goalAmount, setGoalAmount] = useState("");
const [goalName, setGoalName] = useState("");
async function publishGoal() {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (pub) {
const evNew = await pub.generic((eb) => {
eb.kind(GOAL)
diff --git a/src/element/send-zap.css b/src/element/send-zap.css
index 97bb855..c203ecb 100644
--- a/src/element/send-zap.css
+++ b/src/element/send-zap.css
@@ -4,19 +4,6 @@
flex-direction: column;
}
-.send-zap h3 {
- font-size: 24px;
- font-weight: 500;
- margin: 0;
-}
-
-.send-zap small {
- display: block;
- text-transform: uppercase;
- color: #868686;
- margin-bottom: 12px;
-}
-
.send-zap .amounts {
display: grid;
grid-template-columns: repeat(4, 1fr);
@@ -37,10 +24,6 @@
background: #353535;
}
-.send-zap div.paper {
- background: #262626;
-}
-
.send-zap p {
margin: 0 0 8px 0;
font-weight: 500;
diff --git a/src/element/send-zap.tsx b/src/element/send-zap.tsx
index 9f1bf76..c56eb8e 100644
--- a/src/element/send-zap.tsx
+++ b/src/element/send-zap.tsx
@@ -11,6 +11,7 @@ import { Icon } from "./icon";
import AsyncButton from "./async-button";
import { Relays } from "index";
import QrCode from "./qr-code";
+import { useLogin } from "hooks/login";
export interface LNURLLike {
get name(): string;
@@ -52,6 +53,7 @@ export function SendZaps({
const [amount, setAmount] = useState(satsAmounts[0]);
const [comment, setComment] = useState("");
const [invoice, setInvoice] = useState("");
+ const login = useLogin();
const name = targetName ?? svc?.name;
async function loadService(lnurl: string) {
@@ -72,7 +74,7 @@ export function SendZaps({
async function send() {
if (!svc) return;
- let pub = await EventPublisher.nip7();
+ let pub = login?.publisher();
let isAnon = false;
if (!pub) {
pub = EventPublisher.privateKey(bytesToHex(secp256k1.utils.randomPrivateKey()));
diff --git a/src/element/stream-editor.tsx b/src/element/stream-editor.tsx
index 6384d66..4892b11 100644
--- a/src/element/stream-editor.tsx
+++ b/src/element/stream-editor.tsx
@@ -1,12 +1,13 @@
import "./stream-editor.css";
import { useEffect, useState, useCallback } from "react";
-import { EventPublisher, NostrEvent } from "@snort/system";
+import { NostrEvent } from "@snort/system";
import { unixNow } from "@snort/shared";
import { TagsInput } from "react-tag-input-component";
import AsyncButton from "./async-button";
import { StreamState } from "../index";
import { findTag } from "../utils";
+import { useLogin } from "hooks/login";
export interface StreamEditorProps {
ev?: NostrEvent;
@@ -32,6 +33,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
const [tags, setTags] = useState([]);
const [contentWarning, setContentWarning] = useState(false);
const [isValid, setIsValid] = useState(false);
+ const login = useLogin();
useEffect(() => {
setTitle(findTag(ev, "title") ?? "");
@@ -62,7 +64,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
}, [validate, title, summary, image, stream]);
async function publishStream() {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (pub) {
const evNew = await pub.generic((eb) => {
const now = unixNow();
@@ -83,7 +85,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
for (const tx of tags) {
eb.tag(["t", tx.trim()]);
}
- if(contentWarning) {
+ if (contentWarning) {
eb.tag(["content-warning", "nsfw"])
}
return eb;
diff --git a/src/element/write-message.tsx b/src/element/write-message.tsx
index 9d3d763..9739f93 100644
--- a/src/element/write-message.tsx
+++ b/src/element/write-message.tsx
@@ -1,8 +1,7 @@
-import { NostrLink, EventPublisher, EventKind } from "@snort/system";
-import { useRef, useState, useMemo, ChangeEvent } from "react";
+import { NostrLink, EventKind } from "@snort/system";
+import { useRef, useState, ChangeEvent } from "react";
import { LIVE_STREAM_CHAT } from "../const";
-import useEmoji, { packId } from "../hooks/emoji";
import { useLogin } from "../hooks/login";
import { System } from "../index";
import AsyncButton from "./async-button";
@@ -32,7 +31,7 @@ export function WriteMessage({
const leftOffset = ref.current?.getBoundingClientRect().left;
async function sendChatMessage() {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (chat.length > 1) {
let emojiNames = new Set();
diff --git a/src/hooks/copy.tsx b/src/hooks/copy.tsx
new file mode 100644
index 0000000..dde650b
--- /dev/null
+++ b/src/hooks/copy.tsx
@@ -0,0 +1,20 @@
+import { useState } from "react";
+
+export const useCopy = (timeout = 2000) => {
+ const [error, setError] = useState(false);
+ const [copied, setCopied] = useState(false);
+
+ const copy = async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopied(true);
+ setError(false);
+ } catch (error) {
+ setError(true);
+ }
+
+ setTimeout(() => setCopied(false), timeout);
+ };
+
+ return { error, copied, copy };
+};
diff --git a/src/hooks/login.ts b/src/hooks/login.ts
index 77c8c07..d7662e6 100644
--- a/src/hooks/login.ts
+++ b/src/hooks/login.ts
@@ -1,9 +1,17 @@
import { Login } from "index";
+import { getPublisher } from "login";
import { useSyncExternalStore } from "react";
export function useLogin() {
- return useSyncExternalStore(
+ const session = useSyncExternalStore(
(c) => Login.hook(c),
() => Login.snapshot()
);
+ if (!session) return;
+ return {
+ ...session,
+ publisher: () => {
+ return getPublisher(session);
+ }
+ }
}
diff --git a/src/index.css b/src/index.css
index 24e6a3e..8ff23ef 100644
--- a/src/index.css
+++ b/src/index.css
@@ -33,6 +33,11 @@ a {
flex-direction: column;
}
+.f-center {
+ align-items: center;
+ justify-content: center;
+}
+
.pill {
background: #171717;
padding: 4px 8px;
@@ -164,4 +169,26 @@ div.paper {
.border-warning {
border: 1px solid #FF563F;
+}
+
+.dialog-content {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.dialog-content div.paper {
+ background: #262626;
+}
+
+.dialog-content h3 {
+ font-size: 24px;
+ font-weight: 500;
+ margin: 0;
+}
+
+.dialog-content small {
+ display: block;
+ color: #868686;
+ margin: 6px;
}
\ No newline at end of file
diff --git a/src/login.ts b/src/login.ts
index 0603211..435e26e 100644
--- a/src/login.ts
+++ b/src/login.ts
@@ -1,7 +1,17 @@
+import { bytesToHex } from "@noble/curves/abstract/utils";
+import { schnorr } from "@noble/curves/secp256k1";
import { ExternalStore } from "@snort/shared";
+import { EventPublisher, Nip7Signer, PrivateKeySigner } from "@snort/system";
+
+export enum LoginType {
+ Nip7 = "nip7",
+ PrivateKey = "private-key"
+}
export interface LoginSession {
+ type: LoginType;
pubkey: string;
+ privateKey?: string;
follows: string[];
}
@@ -13,19 +23,49 @@ export class LoginStore extends ExternalStore {
const json = window.localStorage.getItem("session");
if (json) {
this.#session = JSON.parse(json);
+ if (this.#session) {
+ this.#session.type ??= LoginType.Nip7;
+
+ }
}
}
- loginWithPubkey(pk: string) {
+ loginWithPubkey(pk: string, type = LoginType.Nip7) {
this.#session = {
+ type,
pubkey: pk,
follows: [],
};
- window.localStorage.setItem("session", JSON.stringify(this.#session));
- this.notifyChange();
+ this.#save();
+ }
+
+ loginWithPrivateKey(key: string) {
+ this.#session = {
+ type: LoginType.PrivateKey,
+ pubkey: bytesToHex(schnorr.getPublicKey(key)),
+ privateKey: key,
+ follows: [],
+ };
+ this.#save();
}
takeSnapshot() {
return this.#session ? { ...this.#session } : undefined;
}
+
+ #save() {
+ window.localStorage.setItem("session", JSON.stringify(this.#session));
+ this.notifyChange();
+ }
}
+
+export function getPublisher(session: LoginSession) {
+ switch (session?.type) {
+ case LoginType.Nip7: {
+ return new EventPublisher(new Nip7Signer(), session.pubkey);
+ }
+ case LoginType.PrivateKey: {
+ return new EventPublisher(new PrivateKeySigner(session.privateKey!), session.pubkey);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx
index 0fc762e..49fb937 100644
--- a/src/pages/layout.tsx
+++ b/src/pages/layout.tsx
@@ -1,27 +1,19 @@
-import { Icon } from "element/icon";
import "./layout.css";
-import {
- EventPublisher,
-} from "@snort/system";
+import { useState } from "react";
+import * as Dialog from "@radix-ui/react-dialog";
import { Outlet, useNavigate, useLocation, Link } from "react-router-dom";
-import AsyncButton from "element/async-button";
-import { Login } from "index";
+
+import { Icon } from "element/icon";
import { useLogin } from "hooks/login";
import { Profile } from "element/profile";
import { NewStreamDialog } from "element/new-stream";
-import { useState } from "react";
+import { LoginSignup } from "element/login-signup";
export function LayoutPage() {
const navigate = useNavigate();
const login = useLogin();
const location = useLocation();
-
- async function doLogin() {
- const pub = await EventPublisher.nip7();
- if (pub) {
- Login.loginWithPubkey(pub.pubKey);
- }
- }
+ const [showLogin, setShowLogin] = useState(true);
function loggedIn() {
if (!login) return;
@@ -43,14 +35,20 @@ export function LayoutPage() {
function loggedOut() {
if (login) return;
- return (
- <>
-
+ return
+
+
- >
- );
+
+
+
+
+
+ setShowLogin(false)} />
+
+
+
}
const isNsfw = window.location.pathname === "/nsfw";
@@ -83,6 +81,7 @@ export function LayoutPage() {
{isNsfw && }
+
);
}
diff --git a/src/pages/providers/nostr.tsx b/src/pages/providers/nostr.tsx
index 1283d06..5ae5652 100644
--- a/src/pages/providers/nostr.tsx
+++ b/src/pages/providers/nostr.tsx
@@ -1,10 +1,11 @@
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+
import AsyncButton from "element/async-button";
import { StatePill } from "element/state-pill";
import { StreamState } from "index";
import { StreamProviderInfo, StreamProviderStore } from "providers";
import { Nip103StreamProvider } from "providers/nip103";
-import { useState } from "react";
-import { useNavigate } from "react-router-dom";
export function ConfigureNostrType() {
const [url, setUrl] = useState("");
diff --git a/src/pages/stream-page.tsx b/src/pages/stream-page.tsx
index d825a46..978eb14 100644
--- a/src/pages/stream-page.tsx
+++ b/src/pages/stream-page.tsx
@@ -1,5 +1,5 @@
import "./stream-page.css";
-import { parseNostrLink, TaggedRawEvent, EventPublisher } from "@snort/system";
+import { parseNostrLink, TaggedRawEvent } from "@snort/system";
import { useNavigate, useParams } from "react-router-dom";
import useEventFeed from "hooks/event-feed";
@@ -31,7 +31,7 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedRawEvent }) {
const isMine = ev?.pubkey === login?.pubkey;
async function deleteStream() {
- const pub = await EventPublisher.nip7();
+ const pub = login?.publisher();
if (pub && ev) {
const evDelete = await pub.delete(ev.id);
console.debug(evDelete);
diff --git a/src/providers/nip103.ts b/src/providers/nip103.ts
index 6d6b598..25cac3d 100644
--- a/src/providers/nip103.ts
+++ b/src/providers/nip103.ts
@@ -1,5 +1,7 @@
import { StreamProvider, StreamProviderInfo, StreamProviders } from ".";
-import { EventPublisher, EventKind, NostrEvent } from "@snort/system";
+import { EventKind, NostrEvent } from "@snort/system";
+import { Login } from "index";
+import { getPublisher } from "login";
import { findTag } from "utils";
export class Nip103StreamProvider implements StreamProvider {
@@ -59,8 +61,9 @@ export class Nip103StreamProvider implements StreamProvider {
}
async #getJson(method: "GET" | "POST" | "PATCH", path: string, body?: unknown): Promise {
- const pub = await EventPublisher.nip7();
- if (!pub) throw new Error("No event publisher");
+ const login = Login.snapshot();
+ const pub = login && getPublisher(login);
+ if (!pub) throw new Error("No signer");
const u = `${this.#url}${path}`;
const token = await pub.generic(eb => {
diff --git a/src/utils.ts b/src/utils.ts
index 9556d04..1bb56a3 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -55,4 +55,20 @@ export function eventLink(ev: NostrEvent) {
export function getHost(ev?: NostrEvent) {
return ev?.tags.find(a => a[0] === "p" && a[3] === "host")?.[1] ?? ev?.pubkey ?? "";
+}
+
+export async function openFile(): Promise {
+ return new Promise(resolve => {
+ const elm = document.createElement("input");
+ elm.type = "file";
+ elm.onchange = (e: Event) => {
+ const elm = e.target as HTMLInputElement;
+ if (elm.files) {
+ resolve(elm.files[0]);
+ } else {
+ resolve(undefined);
+ }
+ };
+ elm.click();
+ });
}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 3f212e5..20f7825 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -123,6 +123,7 @@ const config = {
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
modules: ["node_modules", __dirname, path.resolve(__dirname, "src")],
+ fallback: { "crypto": false }
},
};
diff --git a/yarn.lock b/yarn.lock
index c432350..7948384 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2011,6 +2011,13 @@
"@typescript-eslint/types" "5.59.0"
eslint-visitor-keys "^3.3.0"
+"@void-cat/api@^1.0.7":
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/@void-cat/api/-/api-1.0.7.tgz#39564d478dee07398826e7109d6368c68c405426"
+ integrity sha512-0K20PaLnRL0lYLOOn8Sk3J6THdU7ebIHWPR7S8Ytzdi5VGI8468ocackCs0b/gFZvvkwVp0X/Rygxe1/nhch+Q==
+ dependencies:
+ sjcl "^1.0.8"
+
"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
@@ -5861,6 +5868,11 @@ sirv@^1.0.7:
mrmime "^1.0.0"
totalist "^1.0.0"
+sjcl@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.8.tgz#f2ec8d7dc1f0f21b069b8914a41a8f236b0e252a"
+ integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"