发布于 2026-01-06 2 阅读
0

NPM 包的 Web 货币化!monetize-npm-cli 安装用法 API wrapper-coil-extension 安装用法

NPM软件包的Web货币化!

npm 命令行货币化

安装

用法

API

包覆线圈延伸

安装

用法

长期以来,我一直想为社区做出一些有意义的贡献,但始终未能如愿。这次黑客马拉松给了我一个完美的途径来实现这个目标,那就是创建一个 NPM 包货币化的方法!

我建造的

我构建了 2 个 npm 包

  1. monetize-npm-cli npm
  2. wrapper-coil-extension npm

monetize-npm-cli

引用其自述文件

monetize-npm-cli是一个模块化 CLI,它使用 Web Monetization API 和不同的提供商来帮助 npm 包实现货币化。

事实正是如此。

我(第一次!)构建了一个 CLI,它允许你在容器般的环境中运行你的应用程序,而如果应用程序不去查找,它不一定知道这些容器的存在。

node index.js=>monetize-npm-cli index.js这样你就可以出发了!

它会找到package.json你的主项目,然后在文件夹内搜索node_modules。任何package.json找到的具有该键值的项目webMonetization都会被选中进行货币化。

{
  "webMonetization": {
    "wallet": "$yourWalletAddressGoesHere"
  }
}
Enter fullscreen mode Exit fullscreen mode

添加此功能package.json即可实现软件包的货币化。


我希望 API 尽可能与浏览器已有的 API 相似,但为了适应不同的环境,必须做出一些更改。

document随之而来globalThis的是以下变化

获取状态

document.monetization.state=>globalThis.monetization.getState(name, version)

name并在每个包version中定义。package.json

webMonetization只有包含密钥的软件包package.json才能在此处访问。

添加事件监听器

可以设置四个监听monetizationpending,,,monetizationstartmonetizationstopmonetizationprogress

让我们通过 listenerIdentifier 来识别它们。

document.monetization.addEventListener(listenerIdentifier, foo)=>globalThis.monetization.addEventListener(name, version, listenerIdentifier, foo)

移除事件监听器

globalThis.monetization.removeEventListener(name, version, listenerIdentifier, foo)

如果没有传递 foo,则删除该包的所有监听器。


这些方法可以在应用程序内部的任何位置使用,并且在检查已安装的软件包是否globalThis.monetization存在之后,可以相应地使用这些方法。

globalThis.monetization它本身是实际使用对象的代理,使其难以篡改。


还记得我说过这个 CLI 是模块化的吗?那是因为它只需做极少的改动就能轻松添加和使用许多不同的提供商!

这就是它wrapper-coil-extension发挥作用的地方。


wrapper-coil-extension

引用其自述文件

wrapper-coil-extension是 Coil 的 Web Monetization 浏览器扩展的封装,允许从 node.js 中使用它。

由于我需要一个提供商来配合我创建的 CLI,而目前没有任何提供商提供 API 来实现这一点,所以我不得不另辟蹊径,利用现有的提供商,因此我围绕 Coil 的扩展构建了一个包装器,使我能够做到这一点。

由于该扩展程序目前不支持同时对多个标签页进行盈利, 所有符合条件的包裹都会被逐一筛选,并打开一个包含其钱包的网页一段时间(时间可由用户设定)。这样就可以向相应的包裹所有者发送款项。已修复v0.0.7。采用概率收益分成机制,随机选择一个套餐,每个套餐限时65秒,并按此机制进行盈利。此过程会重复进行,直到应用关闭为止。


因为 Coil 的扩展程序并非为此类场景而设计, 有些事情并没有按预期进行。目前一切运行正常,更多信息请点击此处查看。

另一个问题是,当打开新标签页而关闭旧标签页(以便推广其他应用)时,Chromium 会抢占焦点。但由于这是在生产环境中运行的,所以这其实不算什么问题。完美bug=>feature情况 XD现在指针会在同一标签页内动态变化,从而解决了这个问题。

由于其模块化特性monetize-npm-cli,随着越来越多的服务提供商涌现并提供不同的盈利模式,他们的模块可以轻松集成到系统中,只需进行少量更改。您可以在这里monetize-npm-cli查看如何创建此类模块


这比什么更好?npm fund

你可能从打开这篇文章的那一刻起就一直有这个问题。没错,我们都见过npm fund安装支持该功能的软件包时弹出的提示框。但大多数人都没有尝试运行这个命令,也没有点击它提供的链接。之后,你还得费一番功夫才能找到付费支持开发者的方法,这体验很糟糕,甚至会让原本愿意付费的人打消念头。

好了,现在情况就不同了。步骤简化为:只需全局安装此软件包,登录一次服务提供商,然后运行使用该软件包的应用程序即可。


这还能带来一些其他好的变化。

  1. 随着开发者们能够将爱好变成收入来源,更多软件包正在积极开发中。
  2. 谨慎安装软件包,防止安装不必要的软件包。
  3. 进一步思考依赖循环,如果将同一软件包的两个不兼容的版本列为依赖项,则最终可能会安装两次,从而导致两次盈利。

提交类别:

难点来了。在准备作品的过程中,我一直在努力确定它应该归入哪个类别,但至今仍然无法确定。

  1. 基础技术——这是一个用于网络盈利的模板,也是一个插件(?)
  2. 创意催化剂——它利用现有技术寻找分发内容和实现内容变现的方法。
  3. 激动人心的实验——在浏览器之外运行的网页变现!你敢说这不是激动人心的实验吗?

演示

您只需输入以下内容即可跟随此演示进行操作。

npm install -g monetize-npm-cli
Enter fullscreen mode Exit fullscreen mode

首先,我们来检查一下软件包是否已正确安装。

monetize-npm-cli -v
Enter fullscreen mode Exit fullscreen mode

版本图像

我们去帮助页面看看。

monetize-npm-cli -h
Enter fullscreen mode Exit fullscreen mode

帮助图片

要将任何套餐货币化,我们需要先登录我们的服务提供商。

monetize-npm-cli --login
Enter fullscreen mode Exit fullscreen mode

这将打开一个浏览器窗口,您可以在其中使用您的凭据登录。

登录浏览器

登录成功后,我们将在终端上看到以下内容。

登录终端

在这个演示中,我手动向一些 npm 包中添加了webMonetization各种键。package.json

我们来试着列出这些包裹。

monetize-npm-cli --list --expand
Enter fullscreen mode Exit fullscreen mode

你可能会看到类似这样的情况。

列表终端

让我们添加一些globalThis.monetization从容器内运行的应用程序的访问权限。

添加代码片段

现在我们来尝试运行一下这个应用。

monetize-npm-cli index.js
Enter fullscreen mode Exit fullscreen mode

base64url 一旦开始收费

base64usl 支付

我们可以在控制台中看到我们设置的事件已触发。

终端输出

代码链接

monetize-npm-cli

GitHub 标志 projectescape / monetize-npm-cli

一个 CLI,可帮助使用 Web Monetization API 对 npm 包进行货币化。

npm 命令行货币化

monetize-npm-cli是一个模块化 CLI,它使用 Web Monetization API 和不同的提供商来帮助 npm 包实现货币化。


安装

npm install -g monetize-npm-cli
Enter fullscreen mode Exit fullscreen mode

用法

运行文件

要在运行应用程序的同时通过支持的 npm 包实现盈利。

monetize-npm-cli yourFile.js
Enter fullscreen mode Exit fullscreen mode

帮助

查看包含所有详细信息的帮助页面

monetize-npm-cli --help
Enter fullscreen mode Exit fullscreen mode

登录您的服务提供商

登录您的网站变现服务提供商

monetize-npm-cli --login
Enter fullscreen mode Exit fullscreen mode

如果没有提供提供程序,则默认使用 coil-extension。更多详情请参阅帮助文档。


从您的服务提供商处注销

要从您的网站变现服务提供商处注销

monetize-npm-cli --logout
Enter fullscreen mode Exit fullscreen mode

如果没有提供提供程序,则默认使用 coil-extension。更多详情请参阅帮助文档。


列出软件包

列出所有支持网站货币化的软件包

monetize-npm-cli --list
Enter fullscreen mode Exit fullscreen mode

使用 help 获取支持的命令的完整列表


API

此 CLI 的目标是尽可能地模仿此处document.monetization提供的 Web 货币化 API,而不是用户……

wrapper-coil-extension

GitHub 标志 projectescape / wrapper-coil-extension

一个用于封装 Coil 的 Web 货币化扩展的脚本,使其能够在 Node.js 环境下运行。

包覆线圈延伸

wrapper-coil-extension是 Coil 的 Web Monetization 浏览器扩展的封装,允许从 node.js 中使用它。


安装

npm install --save wrapper-coil-extension
Enter fullscreen mode Exit fullscreen mode

用法

const { login, logout, monetize } = require("wrapper-coil-extension");

// To Login with your Coil Account

login();

// To Logout

logout();

// To start Monetization

monetize(monetizationPackages);
Enter fullscreen mode Exit fullscreen mode

暂停

(已折旧)

由于v0.0.7不再使用超时,而是采用概率收益分成而不是循环遍历数据包,因此不再使用超时。


货币化套餐

monetizationPackages 是一个由以下方式传递的对象:monetize-npm-cli

// monetizationPackages
{
    packages:[
        {
          name: "",
          version: "",
          webMonetization: {
              wallet:""
          },
          state: "",
          monetizationpending: [],
          monetizationstart: [],
          monetizationstop: [],
          monetizationprogress: [],
        }
    ]
Enter fullscreen mode Exit fullscreen mode

我是如何搭建它的

开发这个项目真的很有趣。构建命令行界面和自动化网站对我来说完全是全新的体验。

monetize-npm-cli

我解析了参数minimist并将其用于kleur日志记录。

fast-glob用于package.json在保持操作系统间兼容性的前提下进行查找。

最难的部分在于设计变现对象,因为我需要处理监听器、包及其状态,同时还要保证部分内容的私有性globalThis.monetization,以及将对象传递给提供者模块。经过大量的研究,我深入了解了 JavaScript 对象,并最终设计出了这个方案。

const monetization = (() => {
  let packages = [];
  const walletHash = {};
  const nameHash = {};

  return {
    get packages() {
      return packages;
    },
    set packages(val) {
      packages = val;
      val.forEach((p, index) => {
        if (walletHash[p.webMonetization.wallet] === undefined)
          walletHash[p.webMonetization.wallet] = [index];
        else walletHash[p.webMonetization.wallet].push(index);

        nameHash[`${p.name}@${p.version}`] = index;
      });
    },
    getState(name, version) {
      if (nameHash[`${name}@${version}`] !== undefined) {
        return packages[nameHash[`${name}@${version}`]].state;
      }
      console.log(`No package ${name}@${version} found\n`);
      return undefined;
    },
    addEventListener(name, version, listener, foo) {
      if (
        !(
          listener === "monetizationpending" ||
          listener === "monetizationstart" ||
          listener === "monetizationstop" ||
          listener === "monetizationprogress"
        )
      ) {
        console.log(`${listener} is not a valid event name\n`);
        return false;
      }
      if (nameHash[`${name}@${version}`] !== undefined) {
        packages[nameHash[`${name}@${version}`]][listener].push(foo);
        return true;
      }
      console.log(`No package ${name}@${version} found\n`);
      return false;
    },
    removeEventListener(name, version, listener, foo = undefined) {
      if (
        !(
          listener === "monetizationpending" ||
          listener === "monetizationstart" ||
          listener === "monetizationstop" ||
          listener === "monetizationprogress"
        )
      ) {
        console.log(`${listener} is not a valid event name\n`);
        return false;
      }
      if (nameHash[`${name}@${version}`] !== undefined) {
        if (!foo) {
          packages[nameHash[`${name}@${version}`]][listener] = [];
        } else {
          packages[nameHash[`${name}@${version}`]][listener] = packages[
            nameHash[`${name}@${version}`]
          ][listener].filter((found) => foo !== found);
        }
        return true;
      }
      console.log(`No package ${name}@${version} found\n`);
      return false;
    },
    invokeEventListener(data) {
      walletHash[data.detail.paymentPointer].forEach((index) => {
        packages[index].state =
          data.type === "monetizationstart" ||
          data.type === "monetizationprogress"
            ? "started"
            : data.type === "monetizationpending"
            ? "pending"
            : "stopped";
        packages[index][data.type].forEach((listener) => {
          listener(data);
        });
      });
    },
  };
})();
Enter fullscreen mode Exit fullscreen mode

globalThis.monetization它是通过类似这样的代理实现的

globalThis.monetization = new Proxy(monetization, {
  set: () => {
    console.log("Not allowed to mutate values\n");
  },
  get(target, key, receiver) {
    if (
      key === "getState" ||
      key === "addEventListener" ||
      key === "removeEventListener"
    ) {
      return Reflect.get(...arguments);
    } else {
      console.log(`Not allowed to access monetization.${key}\n`);
      return null;
    }
  },
});
Enter fullscreen mode Exit fullscreen mode

这样既能防止篡改原始对象,又能只展现所需的功能。

模块提供者会收到另一个代理,用于相同的目的。

new Proxy(monetization, {
        set: () => {
          console.log("Not allowed to mutate values\n");
        },
        get(target, key, receiver) {
          if (key === "packages" || key === "invokeEventListener") {
            return Reflect.get(...arguments);
          } else {
            console.log(`Not allowed to access monetization.${key}\n`);
            return null;
          }
        },
      }),
Enter fullscreen mode Exit fullscreen mode

wrapper-coil-extension

这真是太难了。一开始,我尝试通过查看 Coil 在 GitHub 上的代码来逆向工程他们的扩展程序,但对我来说理解和重新编写代码都太难了。我没有 TypeScript 经验,也没有开发过任何浏览器扩展程序,这更是雪上加霜。

然后我找到了puppeteer(感谢@wobsoriano

我浏览了 Coil 的网站,发现他们会在用户登录时设置一个jwt参数。这样就实现了登录和注销功能,因为我只需要将参数存储在本地即可。localStoragejwt

为了实现套餐的盈利,我 遍历了所有已启用盈利功能的软件包设置了概率收益分成机制,并制作了一个 HTML 模板文件,该文件会在 65 秒内填充各个钱包的值。

为了使监听器按预期工作,并保持其功能与浏览器对应功能相似,我们还做了很多工作。

然后,这些页面被提供给puppeteerCoil 的扩展程序,该程序在查看设置的钱包后发送付款。

更多资源/信息

文中已提供所有资源链接。

文章来源:https://dev.to/projectescape/web-monetization-for-npm-packages-3g6o