【p2p、分布式,区块链笔记 DAM】GUN用户认证实例 useGunContext

  • useGunContext(https://github.com/SuaYoo/gundb-react-express-auth-example/blob/main/server/server.js)代码创建了一个上下文(Context)来为整个应用提供 GUN 实例及其用户认证逻辑,在多个组件之间共享 GUN 数据库连接和用户状态数据()。 useGunContext() 钩子提供以下内容:
{
  getGun: Function, // 返回 `gun` 实例
  getUser: Function, // 返回 `gun.user()` 实例
  getCertificate: Function, // 返回当前用户的证书
  setCertificate: Function, // 设置当前用户的证书
  onAuth: Function, // 接收回调函数,在 `gun.user().auth()` 时触发
}

概述

  1. GUN 实例管理

    • 通过 useRef 保持 GUN 实例、用户对象和认证 token 的持久性。
    • 确保整个应用只使用一个 GUN 实例,避免多次初始化。
  2. 用户认证

    • useEffect 中监听 GUN 的 auth 事件,当用户认证成功时,通过 API 获取 accessTokencertificate
    • 支持自动记住用户登录状态(使用 sessionStorage)。
  3. 跨组件共享

    • 通过 GunContext.Provider 将 GUN 实例、用户对象和相关方法传递给整个应用,使各个组件可以轻松访问。

代码解释

1. 引入依赖和createContext函数

import React, { createContext, useContext, useRef, useEffect } from 'react';
import Gun from 'gun/gun';
import 'gun/sea';

const GunContext = createContext({
  getGun: () => {},
  getUser: () => {},
  getCertificate: () => {},
  setCertificate: () => {},
  onAuth: () => () => {},
});
  • createContextcreateContext函数用于创建 Context 对象。Context 允许你在组件树中共享数据,而无需显式地通过每一级组件的 props 进行传递。
  • 使用 Context.Provider提供数据 <MyContext.Provider value={value}> …… </MyContext.Provider>
  • 使用 useContext消费数据:用于在子组件中访问 GunContext 提供的值。

2. GunContextProvider组件

  • 虽然在技术上你可以使用普通对象来存储这些引用,如:var gunRef = {}; gunRef.current = ...。如果你在组件内部直接使用 var gunRef = {},每次组件重新渲染时 gunRef 都会重新初始化。
  • 但 useRef 提供了更好的组件内数据持久化方式,同时确保与 React 的生命周期管理相匹配。
export const GunContextProvider = ({ children }) => {
  // 使用 useRef 保存 GUN 实例、用户对象、认证 token 和回调函数
  const gunRef = useRef();
  const userRef = useRef();
  const certificateRef = useRef();
  const accessTokenRef = useRef();
  const onAuthCbRef = useRef();
  // 初始化 GUN 和用户认证(依赖数组为[],仅在组件挂载时运行)
  useEffect(() => {
    // 调用 Gun() 创建一个实例时,Gun.js 会触发一个 opt 事件。这个事件用于配置 Gun 的上下文(ctx)
    // GUN 的中间件,用于在每个请求的消息头中添加 accessToken
    Gun.on('opt', (ctx) => {
      if (ctx.once) return;
	  // 监听所有 出站消息(即发送到 Gun 网络的请求)
      ctx.on('out', function (msg) {
        const to = this.to;// this.to 是 Gun 内部的下一个中间件,即消息的下一个处理程序。
        // 添加 accessToken 到请求头
        msg.headers = {
          accessToken: accessTokenRef.current,// accessTokenRef.current 是通过 useRef 保存的 访问令牌,用于身份验证
        };
        to.next(msg); // 继续传递到下一个中间件

        // 如果 accessToken 无效,则处理错误(未实现)
        if (msg.err === 'Invalid access token') {
          // 这里可以处理 token 失效的情况,比如刷新 token 或重定向登录页面
        }
      });
    });

    // 初始化 GUN 实例,连接本地服务器
    const gun = Gun(['http://localhost:8765/gun']);

    // 创建用户实例,并使用 sessionStorage 记住用户登录信息
    const user = gun
      .user()
      .recall({ sessionStorage: true });

    // 当用户认证成功时的回调
    gun.on('auth', (...args) => {
      // 如果没有 accessToken,则通过 API 从server获取新的 token
      if (!accessTokenRef.current) {
        user.get('alias').once((username) => {
          fetch('http://localhost:8765/api/tokens', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ username, pub: user.is.pub }),
          })
            .then((resp) => resp.json())
            .then(({ accessToken }) => {
              accessTokenRef.current = accessToken; // 保存 token
            });
        });
      }

      // 如果没有 certificate,则通过 API 获取新的 certificate
      if (!certificateRef.current) {
        user.get('alias').once((username) => {
          fetch('http://localhost:8765/api/certificates', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ username, pub: user.is.pub }),
          })
            .then((resp) => resp.json())
            .then(({ certificate }) => {
              certificateRef.current = certificate; // 保存 certificate
            });
        });
      }

      // 如果有认证回调函数,则调用
      if (onAuthCbRef.current) {
        onAuthCbRef.current(...args);
      }
    });

    // 将 GUN 和用户实例保存到 ref 中,供其他组件使用
    gunRef.current = gun;
    userRef.current = user;
  }, []);
  // 将 GUN 和用户相关方法通过 Context 提供给子组件
  return (
    <GunContext.Provider
      value={{
        getGun: () => gunRef.current, 
        getUser: () => userRef.current,
        getCertificate: () => certificateRef.current,
        setCertificate: (v) => {
          certificateRef.current = v;
        },
        onAuth: (cb) => {
          onAuthCbRef.current = cb;
        },
      }}
    >
      {children}
    </GunContext.Provider>
  );
};
  • 注: value中使用函数() => gunRef.current来获取 gunRef.current 确保始终获取最新的值。

3. useGunContext Hook

export default function useGunContext() {
  return useContext(GunContext);
}
  • useGunContext:这是一个自定义 Hook,便于在组件中使用 GunContext 提供的值和方法。例如:
    const { getGun, getUser, onAuth } = useGunContext();
    

使用示例

// index.js
import { GunContextProvider } from './useGunContext';
import App from './App';

// 在较高层级使用 Provider
ReactDOM.render(
  <GunContextProvider> // GunContextProvider 包裹了 App,使得 App 及其子组件能够访问 Context 中的 Gun 实例和用户方法
    <App />
  </GunContextProvider>,
  document.getElementById('root') // 将整个应用挂载到 HTML 页面中 id 为 'root' 的元素上
);
// App.js
import useGunContext from './useGunContext';

const App = () => {
  const { getGun, getUser, onAuth } = useGunContext();

  // 当用户认证成功时执行回调
  onAuth(() => {
    console.log('authenticated!');
  });

  const handleClick = () => {
    getGun().get('ours').put('this');
    getUser().get('mine').put('that');
  };

  return <button onClick={handleClick}>do something</button>;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值