Compare commits

..

1 Commits

Author SHA1 Message Date
52b3c12a46 fix: type errors 2023-08-12 19:36:32 +02:00
42 changed files with 285 additions and 478 deletions

View File

@ -22,4 +22,4 @@ steps:
volumes:
- name: cache
claim:
name: docker-cache
name: docker-cache

View File

@ -1,19 +1,20 @@
module.exports = {
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
root: true,
ignorePatterns: ["build/", "*.test.ts", "*.js"],
env: {
browser: true,
worker: true,
commonjs: true,
node: false,
},
rules: {
"@typescript-eslint/no-non-null-assertion": "error",
"require-await": "error",
eqeqeq: "error",
"object-shorthand": "warn",
},
};
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
root: true,
ignorePatterns: ["build/", "*.test.ts", "*.js"],
env: {
browser: true,
worker: true,
commonjs: true,
node: false,
},
"rules": {
"@typescript-eslint/no-non-null-assertion": "error",
"require-await": "error",
"eqeqeq": "error",
"object-shorthand": "warn",
}
};

View File

@ -1,8 +1,8 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";

View File

@ -1,8 +1,8 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";

View File

@ -1,8 +1,8 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../.pnp.cjs";

View File

@ -1,8 +1,8 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";

View File

@ -1,31 +1,29 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
const moduleWrapper = (tsserver) => {
const moduleWrapper = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
const { isAbsolute } = require(`path`);
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = (str) => str.startsWith("portal:/");
const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = str => str.startsWith("portal:/");
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const dependencyTreeRoots = new Set(
pnpApi.getDependencyTreeRoots().map((locator) => {
return `${locator.name}@${locator.reference}`;
})
);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
@ -33,11 +31,7 @@ const moduleWrapper = (tsserver) => {
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (
isAbsolute(str) &&
!str.match(/^\^?(zip:|\/zip\/)/) &&
(str.match(/\.zip\//) || isVirtual(str))
) {
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes it much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
@ -51,11 +45,7 @@ const moduleWrapper = (tsserver) => {
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (
locator &&
(dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) ||
isPortal(locator.reference))
) {
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
str = resolved;
}
}
@ -83,55 +73,41 @@ const moduleWrapper = (tsserver) => {
// Before | ^/zip/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
case `vscode <1.61`:
{
str = `^zip:${str}`;
}
break;
case `vscode <1.61`: {
str = `^zip:${str}`;
} break;
case `vscode <1.66`:
{
str = `^/zip/${str}`;
}
break;
case `vscode <1.66`: {
str = `^/zip/${str}`;
} break;
case `vscode <1.68`:
{
str = `^/zip${str}`;
}
break;
case `vscode <1.68`: {
str = `^/zip${str}`;
} break;
case `vscode`:
{
str = `^/zip/${str}`;
}
break;
case `vscode`: {
str = `^/zip/${str}`;
} break;
// To make "go to definition" work,
// We have to resolve the actual file system path from virtual path
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
case `coc-nvim`:
{
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
}
break;
case `coc-nvim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
} break;
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
// We have to resolve the actual file system path from virtual path,
// everything else is up to neovim
case `neovim`:
{
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
}
break;
case `neovim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
} break;
default:
{
str = `zip:${str}`;
}
break;
default: {
str = `zip:${str}`;
} break;
}
} else {
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
@ -143,35 +119,26 @@ const moduleWrapper = (tsserver) => {
function fromEditorPath(str) {
switch (hostInfo) {
case `coc-nvim`:
{
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
}
break;
case `coc-nvim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
} break;
case `neovim`:
{
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
}
break;
case `neovim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
} break;
case `vscode`:
default:
{
return str.replace(
/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/,
process.platform === `win32` ? `` : `/`
);
}
break;
default: {
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
} break;
}
}
@ -183,9 +150,8 @@ const moduleWrapper = (tsserver) => {
// TypeScript already does local loads and if this code is running the user trusts the workspace
// https://github.com/microsoft/vscode/issues/45856
const ConfiguredProject = tsserver.server.ConfiguredProject;
const { enablePluginsWithOptions: originalEnablePluginsWithOptions } =
ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function () {
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
this.projectService.allowLocalPluginLoads = true;
return originalEnablePluginsWithOptions.apply(this, arguments);
};
@ -195,13 +161,12 @@ const moduleWrapper = (tsserver) => {
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const { onMessage: originalOnMessage, send: originalSend } =
Session.prototype;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let hostInfo = `unknown`;
Object.assign(Session.prototype, {
onMessage(/** @type {string | object} */ message) {
const isStringMessage = typeof message === "string";
const isStringMessage = typeof message === 'string';
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
if (
@ -212,12 +177,10 @@ const moduleWrapper = (tsserver) => {
) {
hostInfo = parsedMessage.arguments.hostInfo;
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
const [, major, minor] = (
process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []
).map(Number);
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []).map(Number)
if (major === 1) {
if (minor < 61) {
@ -231,31 +194,21 @@ const moduleWrapper = (tsserver) => {
}
}
const processedMessageJSON = JSON.stringify(
parsedMessage,
(key, value) => {
return typeof value === "string" ? fromEditorPath(value) : value;
}
);
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
return typeof value === 'string' ? fromEditorPath(value) : value;
});
return originalOnMessage.call(
this,
isStringMessage
? processedMessageJSON
: JSON.parse(processedMessageJSON)
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
);
},
send(/** @type {any} */ msg) {
return originalSend.call(
this,
JSON.parse(
JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})
)
);
},
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
return tsserver;

View File

@ -1,31 +1,29 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
const moduleWrapper = (tsserver) => {
const moduleWrapper = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
const { isAbsolute } = require(`path`);
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = (str) => str.startsWith("portal:/");
const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = str => str.startsWith("portal:/");
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const dependencyTreeRoots = new Set(
pnpApi.getDependencyTreeRoots().map((locator) => {
return `${locator.name}@${locator.reference}`;
})
);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
@ -33,11 +31,7 @@ const moduleWrapper = (tsserver) => {
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (
isAbsolute(str) &&
!str.match(/^\^?(zip:|\/zip\/)/) &&
(str.match(/\.zip\//) || isVirtual(str))
) {
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes it much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
@ -51,11 +45,7 @@ const moduleWrapper = (tsserver) => {
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (
locator &&
(dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) ||
isPortal(locator.reference))
) {
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
str = resolved;
}
}
@ -83,55 +73,41 @@ const moduleWrapper = (tsserver) => {
// Before | ^/zip/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
case `vscode <1.61`:
{
str = `^zip:${str}`;
}
break;
case `vscode <1.61`: {
str = `^zip:${str}`;
} break;
case `vscode <1.66`:
{
str = `^/zip/${str}`;
}
break;
case `vscode <1.66`: {
str = `^/zip/${str}`;
} break;
case `vscode <1.68`:
{
str = `^/zip${str}`;
}
break;
case `vscode <1.68`: {
str = `^/zip${str}`;
} break;
case `vscode`:
{
str = `^/zip/${str}`;
}
break;
case `vscode`: {
str = `^/zip/${str}`;
} break;
// To make "go to definition" work,
// We have to resolve the actual file system path from virtual path
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
case `coc-nvim`:
{
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
}
break;
case `coc-nvim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
} break;
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
// We have to resolve the actual file system path from virtual path,
// everything else is up to neovim
case `neovim`:
{
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
}
break;
case `neovim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
} break;
default:
{
str = `zip:${str}`;
}
break;
default: {
str = `zip:${str}`;
} break;
}
} else {
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
@ -143,35 +119,26 @@ const moduleWrapper = (tsserver) => {
function fromEditorPath(str) {
switch (hostInfo) {
case `coc-nvim`:
{
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
}
break;
case `coc-nvim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
} break;
case `neovim`:
{
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
}
break;
case `neovim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
} break;
case `vscode`:
default:
{
return str.replace(
/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/,
process.platform === `win32` ? `` : `/`
);
}
break;
default: {
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
} break;
}
}
@ -183,9 +150,8 @@ const moduleWrapper = (tsserver) => {
// TypeScript already does local loads and if this code is running the user trusts the workspace
// https://github.com/microsoft/vscode/issues/45856
const ConfiguredProject = tsserver.server.ConfiguredProject;
const { enablePluginsWithOptions: originalEnablePluginsWithOptions } =
ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function () {
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
this.projectService.allowLocalPluginLoads = true;
return originalEnablePluginsWithOptions.apply(this, arguments);
};
@ -195,13 +161,12 @@ const moduleWrapper = (tsserver) => {
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const { onMessage: originalOnMessage, send: originalSend } =
Session.prototype;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let hostInfo = `unknown`;
Object.assign(Session.prototype, {
onMessage(/** @type {string | object} */ message) {
const isStringMessage = typeof message === "string";
const isStringMessage = typeof message === 'string';
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
if (
@ -212,12 +177,10 @@ const moduleWrapper = (tsserver) => {
) {
hostInfo = parsedMessage.arguments.hostInfo;
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
const [, major, minor] = (
process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []
).map(Number);
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []).map(Number)
if (major === 1) {
if (minor < 61) {
@ -231,31 +194,21 @@ const moduleWrapper = (tsserver) => {
}
}
const processedMessageJSON = JSON.stringify(
parsedMessage,
(key, value) => {
return typeof value === "string" ? fromEditorPath(value) : value;
}
);
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
return typeof value === 'string' ? fromEditorPath(value) : value;
});
return originalOnMessage.call(
this,
isStringMessage
? processedMessageJSON
: JSON.parse(processedMessageJSON)
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
);
},
send(/** @type {any} */ msg) {
return originalSend.call(
this,
JSON.parse(
JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})
)
);
},
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
return tsserver;

View File

@ -1,8 +1,8 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire } = require(`module`);
const { resolve } = require(`path`);
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";

View File

@ -1,2 +1,2 @@
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self';
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src *; img-src * data: blob:; font-src https://fonts.gstatic.com; media-src * blob:; script-src 'self';

View File

@ -1,17 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Nostr live streaming" />
<link rel="apple-touch-icon" href="/logo.png" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<title>zap.stream</title>
</head>
<body>
<div id="root"></div>
</body>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Nostr live streaming" />
<link rel="apple-touch-icon" href="/logo.png" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<title>zap.stream</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -1 +1,3 @@
[{ "id": "nsfw", "text": "NSFW" }]
[
{ "id": "nsfw", "text": "NSFW" }
]

View File

@ -120,7 +120,7 @@ export function ChatMessage({
.content(`:${emoji.id}:`)
.tag(["e", ev.id])
.tag(["p", ev.pubkey])
.tag(["emoji", e[1], e[2]]);
.tag(["emoji", e.at(1) as string, e.at(2) as string]);
});
}
}
@ -194,7 +194,10 @@ export function ChatMessage({
<div className="message-reaction-container">
{isCustomEmojiReaction && emoji ? (
<span className="message-reaction">
<EmojiComponent name={emoji[1]} url={emoji[2]} />
<EmojiComponent
name={emoji.at(1) as string}
url={emoji.at(2) as string}
/>
</span>
) : (
<span className="message-reaction">{e}</span>

View File

@ -12,7 +12,9 @@ export function LoggedInFollowButton({
value: string;
}) {
const login = useLogin();
if (!login) return;
if (!login) {
return null;
}
const { tags, content, timestamp } = login.follows;
const follows = tags.filter((t) => t.at(0) === tag);

View File

@ -52,7 +52,7 @@
}
.live-chat > .write-message > div:nth-child(1) {
height: 40px;
height: 32px;
flex-grow: 1;
}
@ -325,6 +325,8 @@
text-transform: lowercase;
color: #fff;
font-size: 12px;
font-family: Outfit;
font-style: normal;
font-weight: 500;
line-height: 18px;
}

View File

@ -8,7 +8,7 @@ import {
parseZap,
encodeTLV,
} from "@snort/system";
import { unixNow, unwrap } from "@snort/shared";
import { unixNow } from "@snort/shared";
import { useEffect, useMemo } from "react";
import uniqBy from "lodash.uniqby";
@ -96,7 +96,7 @@ export function LiveChat({
const login = useLogin();
useEffect(() => {
const pubkeys = [
...new Set(feed.zaps.flatMap((a) => [a.pubkey, unwrap(findTag(a, "p"))])),
...new Set(feed.zaps.flatMap((a) => [a.pubkey, findTag(a, "p") ?? ""])),
];
System.ProfileLoader.TrackMetadata(pubkeys);
return () => System.ProfileLoader.UntrackMetadata(pubkeys);

View File

@ -9,7 +9,6 @@ import { StreamEditor, StreamEditorProps } from "./stream-editor";
import { useNavigate } from "react-router-dom";
import { eventLink, findTag } from "utils";
import { NostrProviderDialog } from "./nostr-provider-dialog";
import { unwrap } from "@snort/shared";
function NewStream({ ev, onFinish }: StreamEditorProps) {
const providers = useStreamProvider();
@ -20,7 +19,7 @@ function NewStream({ ev, onFinish }: StreamEditorProps) {
if (!currentProvider) {
setCurrentProvider(
ev !== undefined
? unwrap(providers.find((a) => a.name.toLowerCase() === "manual"))
? providers.find((a) => a.name.toLowerCase() === "manual")
: providers.at(0)
);
}

View File

@ -6,7 +6,7 @@
.send-zap .amounts {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-columns: repeat(4, 1fr);
justify-content: space-evenly;
gap: 8px;
}

View File

@ -43,11 +43,10 @@ export function SendZaps({
targetName,
onFinish,
}: SendZapsProps) {
const UsdRate = 28_000;
const UsdRate = 30_000;
const satsAmounts = [
21, 69, 121, 221, 420, 1_000, 2_100, 5_000, 6_666, 10_000, 21_000, 42_000,
69_000, 100_000, 210_000, 500_000, 1_000_000,
100, 1_000, 5_000, 10_000, 50_000, 100_000, 500_000, 1_000_000,
];
const usdAmounts = [0.05, 0.5, 2, 5, 10, 50, 100, 200];
const [isFiat, setIsFiat] = useState(false);

View File

@ -1,11 +1,9 @@
import { Menu, MenuItem } from "@szhsin/react-menu";
import * as Dialog from "@radix-ui/react-dialog";
import { unwrap } from "@snort/shared";
import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
import { Icon } from "./icon";
import { useState } from "react";
import { Textarea } from "./textarea";
import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
import { findTag } from "utils";
import AsyncButton from "./async-button";
import { useLogin } from "hooks/login";
@ -20,7 +18,7 @@ export function ShareMenu({ ev }: { ev: NostrEvent }) {
const naddr = encodeTLV(
NostrPrefix.Address,
unwrap(findTag(ev, "d")),
findTag(ev, "d") ?? "",
undefined,
ev.kind,
ev.pubkey

View File

@ -100,7 +100,7 @@ function Card({ canEdit, ev, cards }: CardProps) {
);
function findTagByIdentifier(d: string) {
return tags.find((t) => t[1].endsWith(`:${d}`));
return tags.find((t) => (t.at(1) as string).endsWith(`:${d}`));
}
const [dropStyle, dropRef] = useDrop(
@ -293,7 +293,9 @@ function EditCard({ card, cards }: EditCardProps) {
async function onCancel() {
const pub = login?.publisher();
if (pub) {
const newTags = tags.filter((t) => !t[1].endsWith(`:${identifier}`));
const newTags = tags.filter(
(t) => !(t.at(1) as string).endsWith(`:${identifier}`)
);
const userCardsEv = await pub.generic((eb) => {
eb.kind(USER_CARDS).content("");
for (const tag of newTags) {

View File

@ -50,10 +50,10 @@ interface TextareaProps {
}
export function Textarea({ emojis, ...props }: TextareaProps) {
const userDataProvider = async (token: string) => {
const userDataProvider = (token: string) => {
const cache = System.ProfileLoader.Cache;
if (cache instanceof UserProfileCache) {
return await cache.search(token);
return cache.search(token);
}
};

View File

@ -1,84 +0,0 @@
/* latin-ext */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(outfit_400_latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(outfit_400_latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(outfit_500_latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(outfit_500_latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(outfit_600_latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(outfit_600_latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(outfit_700_latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Outfit";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(outfit_700_latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@ -11,6 +11,7 @@ import { useRequestBuilder } from "@snort/system-react";
import { USER_CARDS, CARD } from "const";
import { findTag } from "utils";
import { System } from "index";
import { getAddresses } from "utils";
export function useUserCards(
pubkey: string,
@ -29,16 +30,7 @@ export function useUserCards(
const subRelated = useMemo(() => {
if (!pubkey) return null;
const splitted = related.map((t) => t[1].split(":"));
const authors = splitted
.map((s) => s.at(1))
.filter((s) => s)
.map((s) => s as string);
const identifiers = splitted
.map((s) => s.at(2))
.filter((s) => s)
.map((s) => s as string);
const { authors, identifiers } = getAddresses(related);
const rb = new RequestBuilder(`cards:${pubkey}`);
rb.withOptions({ leaveOpen })
.withFilter()
@ -58,7 +50,8 @@ export function useUserCards(
const cards = useMemo(() => {
return related
.map((t) => {
const [k, pubkey, identifier] = t[1].split(":");
const ref = t.at(1) as string;
const [k, pubkey, identifier] = ref.split(":");
const kind = Number(k);
return (data ?? []).find(
(e) =>
@ -104,15 +97,7 @@ export function useCards(pubkey: string, leaveOpen = false): TaggedRawEvent[] {
const subRelated = useMemo(() => {
if (!pubkey) return null;
const splitted = related.map((t) => t[1].split(":"));
const authors = splitted
.map((s) => s.at(1))
.filter((s) => s)
.map((s) => s as string);
const identifiers = splitted
.map((s) => s.at(2))
.filter((s) => s)
.map((s) => s as string);
const { authors, identifiers } = getAddresses(related);
const rb = new RequestBuilder(`cards:${pubkey}`);
rb.withOptions({ leaveOpen })
@ -134,7 +119,8 @@ export function useCards(pubkey: string, leaveOpen = false): TaggedRawEvent[] {
const cards = useMemo(() => {
return related
.map((t) => {
const [k, pubkey, identifier] = t[1].split(":");
const ref = t.at(1) as string;
const [k, pubkey, identifier] = ref.split(":");
const kind = Number(k);
return cardEvents.find(
(e) =>

View File

@ -45,7 +45,11 @@ export function useUserEmojiPacks(pubkey?: string, userEmoji?: Tags) {
const subRelated = useMemo(() => {
if (!pubkey) return null;
const splitted = related.map((t) => t[1].split(":"));
const splitted = related
.map((t) => t.at(1))
.filter((t) => t)
.map((t) => t as string)
.map((t) => t.split(":"));
const authors = splitted
.map((s) => s.at(1))
.filter((s) => s)

View File

@ -9,7 +9,6 @@ import {
parseZap,
} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { unwrap } from "@snort/shared";
import { GOAL } from "const";
import { System } from "index";
@ -37,15 +36,14 @@ export function useZaps(goal: NostrEvent, leaveOpen = false) {
);
}
export function useZapGoal(host: string, link?: NostrLink, leaveOpen = false) {
export function useZapGoal(host: string, link: NostrLink, leaveOpen = false) {
const sub = useMemo(() => {
if (!link) return null;
const b = new RequestBuilder(`goals:${host.slice(0, 12)}`);
b.withOptions({ leaveOpen });
b.withFilter()
.kinds([GOAL])
.authors([host])
.tag("a", [`${link.kind}:${unwrap(link.author)}:${link.id}`]);
.tag("a", [`${link.kind}:${link.author as string}:${link.id}`]);
return b;
}, [link, leaveOpen]);

View File

@ -1,6 +1,5 @@
import "@szhsin/react-menu/dist/index.css";
import "./index.css";
import "./fonts/outfit/outfit.css";
import React from "react";
import ReactDOM from "react-dom/client";
@ -11,7 +10,7 @@ import { RootPage } from "pages/root";
import { TagPage } from "pages/tag";
import { LayoutPage } from "pages/layout";
import { ProfilePage } from "pages/profile-page";
import { StreamPageHandler } from "pages/stream-page";
import { StreamPage } from "pages/stream-page";
import { ChatPopout } from "pages/chat-popout";
import { LoginStore } from "login";
import { StreamProvidersPage } from "pages/providers";
@ -54,7 +53,7 @@ const router = createBrowserRouter([
},
{
path: "/:id",
element: <StreamPageHandler />,
element: <StreamPage />,
},
{
path: "/providers/:id?",

View File

@ -1,6 +1,6 @@
import { bytesToHex } from "@noble/curves/abstract/utils";
import { schnorr } from "@noble/curves/secp256k1";
import { ExternalStore, unwrap } from "@snort/shared";
import { ExternalStore } from "@snort/shared";
import { EventPublisher, Nip7Signer, PrivateKeySigner } from "@snort/system";
import type { EmojiPack, Tags } from "types";
@ -131,7 +131,7 @@ export function getPublisher(session: LoginSession) {
}
case LoginType.PrivateKey: {
return new EventPublisher(
new PrivateKeySigner(unwrap(session.privateKey)),
new PrivateKeySigner(session.privateKey as string),
session.pubkey
);
}

View File

@ -2,13 +2,12 @@ import "./chat-popout.css";
import { LiveChat } from "element/live-chat";
import { useParams } from "react-router-dom";
import { NostrPrefix, encodeTLV, parseNostrLink } from "@snort/system";
import { unwrap } from "@snort/shared";
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
import { findTag } from "utils";
export function ChatPopout() {
const params = useParams();
const link = parseNostrLink(unwrap(params.id));
const link = parseNostrLink(params.id as string);
const ev = useCurrentStreamFeed(link, true);
const lnk = parseNostrLink(

View File

@ -10,7 +10,6 @@ import {
encodeTLV,
} from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { unwrap } from "@snort/shared";
import { Profile } from "element/profile";
import { Icon } from "element/icon";
import { SendZapsDialog } from "element/send-zap";
@ -53,7 +52,7 @@ const defaultBanner = "https://void.cat/d/Hn1AdN5UKmceuDkgDW847q.webp";
export function ProfilePage() {
const navigate = useNavigate();
const params = useParams();
const link = parseNostrLink(unwrap(params.npub));
const link = parseNostrLink(params.npub as string);
const placeholder = usePlaceholder(link.id);
const profile = useUserProfile(System, link.id);
const zapTarget = profile?.lud16 ?? profile?.lud06;

View File

@ -1,6 +1,5 @@
import "./stream-page.css";
import { NostrLink, NostrPrefix, TaggedRawEvent, tryParseNostrLink } from "@snort/system";
import { fetchNip05Pubkey } from "@snort/shared";
import { parseNostrLink, TaggedRawEvent } from "@snort/system";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { Helmet } from "react-helmet";
@ -10,7 +9,6 @@ import {
findTag,
getEventFromLocationState,
getHost,
hexToBech32,
} from "utils";
import { Profile, getName } from "element/profile";
import { LiveChat } from "element/live-chat";
@ -33,7 +31,6 @@ import {
isContentWarningAccepted,
} from "element/content-warning";
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
import { useEffect, useState } from "react";
function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedRawEvent }) {
const login = useLogin();
@ -113,41 +110,14 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedRawEvent }) {
);
}
export function StreamPageHandler() {
export function StreamPage() {
const params = useParams();
const location = useLocation();
const evPreload = getEventFromLocationState(location.state);
const [link, setLink] = useState<NostrLink>();
useEffect(() => {
if (params.id) {
const parsedLink = tryParseNostrLink(params.id);
if (parsedLink) {
setLink(parsedLink);
} else {
const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@zap.stream`).split("@");
fetchNip05Pubkey(handle, domain).then(d => {
if (d) {
setLink({
id: d,
type: NostrPrefix.PublicKey,
encode: () => hexToBech32(NostrPrefix.PublicKey, d)
} as NostrLink);
}
})
}
}
}, [params.id]);
if (link) {
return <StreamPage link={link} evPreload={evPreload} />
}
}
export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent, link: NostrLink }) {
const link = parseNostrLink(params.id as string);
const ev = useCurrentStreamFeed(link, true, evPreload);
const host = getHost(ev);
const goal = useZapGoal(host, createNostrLink(ev), true);
const goal = useZapGoal(host, link, true);
const title = findTag(ev, "title");
const summary = findTag(ev, "summary");

View File

@ -1,6 +1,5 @@
import "./tag.css";
import { useParams } from "react-router-dom";
import { unwrap } from "@snort/shared";
import { useParams, Navigate } from "react-router-dom";
import { VideoTile } from "element/video-tile";
import { FollowTagButton } from "element/follow-button";
@ -9,17 +8,22 @@ import { useStreamsFeed } from "hooks/live-streams";
export function TagPage() {
const { tag } = useParams();
const { live } = useStreamsFeed(tag);
return (
<div className="tag-page">
<div className="tag-page-header">
<h1>#{tag}</h1>
<FollowTagButton tag={unwrap(tag)} />
if (typeof tag === "string") {
return (
<div className="tag-page">
<div className="tag-page-header">
<h1>#{tag}</h1>
<FollowTagButton tag={tag as string} />
</div>
<div className="video-grid">
{live.map((e) => (
<VideoTile ev={e} key={e.id} />
))}
</div>
</div>
<div className="video-grid">
{live.map((e) => (
<VideoTile ev={e} key={e.id} />
))}
</div>
</div>
);
);
}
return <Navigate to="/" replace={true} />;
}

View File

@ -141,3 +141,20 @@ export function getEventFromLocationState(state: unknown | undefined | null) {
? (state as NostrEvent)
: undefined;
}
export function getAddresses(tags: Tags) {
const splitted = tags
.map((t) => t.at(1))
.filter((t) => t)
.map((t) => t as string)
.map((t) => t.split(":"));
const authors = splitted
.map((s) => s.at(1))
.filter((s) => s)
.map((s) => s as string);
const identifiers = splitted
.map((s) => s.at(2))
.filter((s) => s)
.map((s) => s as string);
return { authors, identifiers };
}

View File

@ -68,8 +68,8 @@ const config = {
}),
new webpack.DefinePlugin({
__XXX: process.env["__XXX"] || JSON.stringify(false),
__XXX_HOST: JSON.stringify("https://xxzap.com"),
}),
__XXX_HOST: JSON.stringify("https://xxzap.com")
})
],
module: {
rules: [
@ -89,12 +89,9 @@ const config = {
babelrc: false,
configFile: false,
presets: [
[
"@babel/preset-env",
{
targets: "defaults",
},
],
["@babel/preset-env", {
targets: "defaults"
}],
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
@ -109,7 +106,7 @@ const config = {
],
},
},
require.resolve("ts-loader"),
require.resolve("ts-loader")
],
},
{
@ -151,7 +148,7 @@ const config = {
aliasFields: ["browser"],
extensions: ["...", ".tsx", ".ts", ".jsx", ".js"],
modules: ["...", __dirname, path.resolve(__dirname, "src")],
fallback: { crypto: false },
fallback: { "crypto": false }
},
};