如何在Svelte中创建带有推送通知的GitHub跟踪器

189 阅读9分钟

在这篇文章中,你将学习如何建立一个GitHub追踪器,当被追踪的仓库有新的问题/PR时,通过发送推送通知来通知用户。

如果你选择加入,GitHub 已经通过电子邮件发送通知了,但许多研究表明,推送通知比电子邮件更能到达用户。通过本教程建立 GitHub 跟踪器后,你将学会如何。

  • 添加一个服务工作者并将跟踪器转换为PWA
  • 订阅推送通知
  • 使用GitHub的API
  • 通过Vercel云功能发送推送事件
  • 使用EasyCron定期获取新问题

前提条件

你需要一些技能和服务来学习本文。

  • 安装Node.js和npm
  • 先前的Svelte知识
  • 一个免费的GitHub账户,因为我们使用的是GitHub API
  • 一个免费的MongoDB Atlas账户,以便在云端使用MongoDB
  • 一个免费的Vercel账户,用于部署应用程序和云功能。

什么是推送通知?

让我们来看看这些所谓的 "推送通知 "是什么。

你一定对普通的通知很熟悉。这些是出现在你屏幕上的文字小气泡,用来通知你一些事情。推送通知与之相似,只是它们不是按需生成的,而是在收到推送事件时生成的。推送通知在应用程序关闭时也能发挥作用,而普通通知则需要你打开应用程序。

推送通知在现代网络浏览器中得到了支持,比如Chrome,它使用了一种叫做服务工作者的东西。服务工作者是独立于浏览器主线程运行的JavaScript小片段,因此,如果你的应用被安装为PWA(渐进式网络应用程序),则可以离线运行。

推送通知在聊天应用中用于通知用户有未读消息,在游戏中用于通知用户游戏事件,在新闻网站中用于通知用户有突发文章,以及其他许多用途。

在你的应用程序中显示推送通知有四个步骤。

  1. 申请许可window.Notification.requestPermission()
  2. 将你的应用程序转换为PWA并安装它
  3. 订阅推送事件
  4. 收到推送事件后,发送通知

第1步:创建跟踪器

让我们在本文中使用Svelte与Vite.js而不是Rollup。顾名思义,Vite比Rollup快,而且还提供了对环境变量的内置支持。要用Svelte和Vite创建一个新项目,请运行这个命令。

npm init vite

选择需要的框架svelte 。如果你愿意,你可以使用TypeScript。我将使用普通的JavaScript。

接下来,cd ,进入项目文件夹,你可以用这些命令将TailwindCSS添加到你的应用程序中,并安装所有的依赖项。

npx svelte-add tailwindcss

# Install packages
yarn install # or npm install

最后,在你最喜欢的代码编辑器中打开该项目,并运行npm run devyarn dev ,在http://localhost:3000 上启动该应用程序

追踪器将如何工作

我们将使用GitHub的API来获取用户所追踪的仓库的问题和拉取请求的列表。用户所追踪的仓库和他们的用户名将被存储在MongoDB数据库中。

第一步将是提示用户他们的用户名。创建src/lib/UsernamePrompt.svelte ,这将是做这件事的组件。这是我为表单设计的用户界面,但你可以随心所欲地设计它。

<script>
  let username = "";
  async function submit() {
    // TODO
  }
</script>

<form
  on:submit|preventDefault="{submit}"
  class="mx-auto min-w-[350px] max-w-[1100px] w-[50%] border border-gray-500 rounded my-4 px-6 py-4"
>
  <h1 class="text-center text-3xl m-4">Enter a username</h1>
  <p class="text-center text-xl m-4">Enter a username to use this tracker</p>

  <input
    type="text"
    class="rounded px-4 py-2 border border-gray-300 w-full outline-none"
    placeholder="Username"
    aria-label="Username"
    bind:value="{username}"
  />

  <button
    class="mt-4 border border-transparent bg-blue-500 text-white rounded px-4 py-2 w-full"
  >
    Submit
  </button>
</form>

像这样在App.svelte 中添加这个组件。

<script>
  import UsernamePrompt from "./lib/UsernamePrompt.svelte";
</script>

<UsernamePrompt />

接下来,让我们添加主跟踪器的用户界面。创建文件src/lib/Tracker.svelte ,并在其中添加以下代码。

<script>
  let repo = "";
  function track() {
    // TODO
  }

  function untrack(repo) {
    // TODO
  }
</script>

<form
  on:submit|preventDefault={track}
  class="mx-auto min-w-[350px] max-w-[1100px] w-[50%] border border-gray-500 rounded my-4 px-6 py-4"
>
  <h1 class="text-center text-3xl m-4">GitHub tracker</h1>

  <input
    type="text"
    class="rounded px-4 py-2 border border-gray-300 w-full outline-none"
    placeholder="Enter the repository's URL"
    aria-label="Repository URL"
    bind:value={repo}
  />
  <button
    class="mt-2 border border-transparent bg-blue-500 text-white rounded px-4 py-2 w-full"
    >Track repository</button
  >

  <h2 class="mt-4 text-2xl">Tracked repositories</h2>
  <ul class="m-2 list-decimal">
    <!-- We'll use a loop to automatically add repositories here later on. -->
    <li class="py-1 flex items-center justify-between">
      <a class="text-gray-500 hover:underline" href="https://github.com/test/test"
        >https://github.com/test/test</a
      >
      <button class="text-red-500 cursor-pointer" on:click={() => untrack("")}
        >Untrack</button
      >
    </li>
  </ul>
</form>

为了测试你的组件,暂时UsernamePrompt 组件换成App.svelte 中新的Tracker 组件。

<script>
  // import UsernamePrompt from "./lib/UsernamePrompt.svelte";
  import Tracker from "./lib/Tracker.svelte";
</script>

<!-- <UsernamePrompt /> -->
<Tracker />

你的屏幕现在应该看起来像这样。

The new tracker component

注意:记得将App.svelte 恢复到它之前的代码!

第二步:设置云功能

我们需要有一个后端服务器来向我们的应用程序发送推送事件。这意味着你需要创建一个新的(也许是)ExpressJS项目,然后将其单独部署。这对一个刚开始尝试推送通知的人来说都会很头疼。

Vercel云函数来拯救你!云函数就像Express路由。它们可以运行代码,并在你获取其URL时给你一个响应。Vercel支持云函数;你只需要在api 文件夹中创建文件。你将使用云函数与MongoDB进行交互,因为在客户端暴露秘密绝不是一件好事。

首先,确保你在MongoDB Atlas中有一个集群。MongoDB有一个免费计划*(M0*),所以如果你还没有的话,一定要创建一个。现在,进入Atlas仪表板侧边栏的数据库访问选项卡。点击右侧的绿色按钮,添加一个新的数据库用户。输入用户的详细信息(不要忘记密码),然后创建用户。

Creating a new Atlas user

要连接到数据库,你将需要连接字符串。把新的用户和密码保存在某个地方,然后到你的群集的概览。点击右边的连接按钮,选择连接你的应用程序作为连接方法。你应该看到一个与下面类似的连接字符串。

Connection string

现在你有了连接字符串,你可以连接到你的数据库,但首先,你需要把当前的应用程序部署到Vercel。最简单的方法是使用GitHub

创建一个新的GitHub仓库,并将你的代码推送给它。接下来,到你的Vercel仪表板,点击新项目按钮。导入你的GitHub仓库,确保框架是Vite,并添加一个名为MONGODB_URL 的环境变量。将其值设置为MongoDB数据库的连接字符串。

一旦你的网站被部署,你需要将你的本地开发命令从yarn dev 改为vercel dev 。运行该命令后,如果你被要求链接到一个现有的项目,请点击

注意:如果你还没有安装Vercel CLI,请确保用npm i -g vercel

像我一样,如果你在使用vitevercel dev 时遇到问题,请确保在Vercel仪表板中把你项目的开发命令vite 改为vite --port $PORT

这将使我们能够在本地使用具有正确环境变量的云函数。

让我们添加一个辅助文件,使我们能够访问MongoDB而不需要打开太多的连接。创建文件api/_mongo.js ,并将以下代码放入其中。api 目录中的文件,如果前缀为_ ,将不会被视为云函数。这允许我们在单独的文件中添加助手和其他逻辑。

const { MongoClient } = require("mongodb");

const mongo = new MongoClient(process.env.MONGODB_URL);

// Export the connection promise
export default mongo.connect();

导出连接承诺而不是主客户端本身将防止我们有多余的连接,因为我们是在一个无服务器平台上工作。

使用CommonJS而不是ESModules

注意到我是如何使用require ,而不是import ?这是因为,在撰写本文时,Vercel云功能支持JavaScript文件中的ESMODULEimport 语句。相反,你需要使用CommonJSrequire 语句。

这里有一个问题。如果你看到我们应用程序的package.json ,你会发现它有一行"type": "module" 。这意味着该项目中的每个JavaScript文件都是一个EsModule。这不是我们想要的,所以为了将api 目录中的所有文件标记为CommonJS文件,所以我们可以使用require 语句,创建api/package.json 并在其中添加这一行。

{
  "type": "commonjs"
}

现在这将允许我们在api 目录中使用require 语句。用这个命令安装MongoDB连接驱动。

# Don't forget to CD!
cd api
npm i mongodb # or use yarn

第3步:添加功能

追踪器,到现在为止,还没有真正发挥作用,所以让我们来解决这个问题。

认证

对于认证,我们需要将用户输入的用户名存储在MongoDB数据库中。

创建一个文件/api/storeusername.js 。这将是一个云函数,将被映射到http://localhost:3000/api/storeusername 。将下面的代码放入其中。

const mongoPromise = require("../src/lib/mongo");
// All cloud functions must export a function that takes a req and res object.
// These objects are similar to their express counterparts.
module.exports = async (req, res) => {
  // TODO
};

接下来,像这样获得MongoDB客户端。

module.exports = async (req, res) =>
  // Wait for the client to connect
  const mongo = await mongoPromise;
}

从请求的正文中提取username

// ...
const { username } = req.body;

// Check if the username is valid
if (typeof username !== "string" || !username.trim()) {
  res.status(400).json({ message: "Please send the username" });
  return;
}

接下来,你需要在数据库中存储这个用户名。

// Get the collection
const usersCol = mongo.db().collection("users");
// Check if the username already exists in the database
if (await usersCol.findOne({ _id: username })) {
  res.status(400).json({ message: "User already exists!" });
  return;
}
// We want the username to be the identifier of the user
await usersCol.insertOne({ _id: username });

// Everything went well :)
res.status(200).json({ message: "Username recorded" });

最后,api/storeusername.js 文件应该是这个样子的。

const mongoPromise = require("./_mongo");

module.exports = async (req, res) => {
  const mongo = await mongoPromise;

  const { username } = req.body;
  if (typeof username !== "string" || !username.trim()) {
    res.status(400).json({ message: "Please send the username" });
    return;
  }

  // Get the collection
  const usersCol = mongo.db().collection("users");

  // Check if the username already exists in the database
  if (await usersCol.findOne({ _id: username })) {
    res.status(400).json({ message: "User already exists!" });
    return;
  }

  // We want the username to be the identifier of the user
  await usersCol.insertOne({ _id: username });

  // Everything went well :)
  res.status(200).json({ message: "Username recorded" });
};

vercel . ,或通过推送到GitHub,将你的应用程序部署到Vercel,你的无服务器功能就可以上线了你可以用这个命令用cURL来测试它。

curl -X POST -H "Content-Type: application/json" -d '{"username": "test"}' https://your-app.vercel.app/api/storeusername

这应该会在users 集合中创建一个新的文档,其中_id 字段是我们刚刚给出的用户名。

The users collection in Atlas

现在剩下的就是在前端获取这个函数了。在src/lib/UsernamePrompt.svelte ,在submit 函数中,首先你需要向云函数发送一个请求,然后把用户名放在localStorage ,这样我们就知道用户是经过认证的。你可以用fetch 函数来发送请求。

async function submit() {
  const res = await fetch("/api/storeusername", {
    body: JSON.stringify({ username }),
    headers: {
      "Content-Type": "application/json",
    },
    method: "POST",
  });
  const data = await res.json();
  if (!res.ok) alert(data.message);
  else {
    // Store the username in localStorage
    localStorage.setItem("username", username);
    // Reload the page
    window.location.reload();
  }
}

我们重新加载页面,因为在App.svelte ,当页面被加载时,我们需要检查在localStorage 中是否有一个用户名。如果有,我们可以跳过UsernamePrompt 屏幕。要做到这一点,在App.sveltescript 标签中添加这段代码。

<script>
  import { onMount } from "svelte";
  import UsernamePrompt from "./lib/UsernamePrompt.svelte";
  import Tracker from "./lib/Tracker.svelte";

  let isLoggedIn = false;
  onMount(() => {
    // If there is a username in the localStorage, set isLoggedIn to true
    isLoggedIn = !!localStorage.getItem("username");
  });
</script>

上述代码将检查localStorage 中是否有用户名,如果存在,则将isLoggedIn 设为true 。接下来,我们所要做的就是更新DOM。就在App.sveltescript 标签下,添加这个。

{#if !isLoggedIn}
<UsernamePrompt />
{:else}
<Tracker />
{/if}

追踪和取消追踪存储库

现在,让我们为追踪器的实际追踪功能添加功能。如果你打开Tracker.svelte ,你会发现有两个函数 -track()untrack() 。这些函数应该分别跟踪和取消跟踪存储库,将它们添加到数据库中。

但在这之前,你还需要添加一些云函数。一个是跟踪版本库,另一个是取消跟踪,最后一个是获取用户的跟踪版本库。

让我们逐一来研究它们。

继续阅读:在Svelte中创建一个带有推送通知的GitHub跟踪器onSitePoint.