cyb/src/services/scripting/runeDeps.ts

import { CyberClient, SigningCyberClient } from '@cybercongress/cyber-js';
import { ProxyMarked } from 'comlink';
import { BehaviorSubject, first, Subject } from 'rxjs';
import { RPC_URL } from 'src/constants/config';
import { getPassportByNickname } from 'src/containers/portal/utils';
import { SenseApi } from 'src/contexts/backend/services/senseApi';
import { Option } from 'src/types';
import { NeuronAddress, ParticleCid } from 'src/types/base';
import { getSearchQuery, searchByHash } from 'src/utils/search/utils';
import DbApiWrapper from '../backend/services/DbApi/DbApi';
import { IpfsApi } from '../backend/workers/background/api/ipfsApi';
import { EmbeddingApi } from '../backend/workers/background/api/mlApi';
import { sendCyberlink } from '../neuron/neuronApi';
import { RuneEngine } from './engine';
import { extractRuneScript } from './helpers';

export type RuneInnerDeps = {
  ipfsApi: Option<IpfsApi>;
  rune: Option<RuneEngine>;
  queryClient: Option<CyberClient>;
  embeddingApi: Option<EmbeddingApi>;
  dbApi: Option<DbApiWrapper>;
  signingClient: Option<SigningCyberClient & ProxyMarked>;
  // signer?: Option<OfflineSigner>;
  senseApi: Option<SenseApi & ProxyMarked>;
  address: Option<NeuronAddress>;
};

type SubjectDeps<T> = {
  [K in keyof T]: BehaviorSubject<T[K]> | Subject<T[K]>;
};

const createRuneDeps = () => {
  const subjectDeps: SubjectDeps<RuneInnerDeps> = {
    // Initialize subjects for each dependency
    ipfsApi: new BehaviorSubject<RuneInnerDeps['ipfsApi']>(undefined),
    rune: new BehaviorSubject<RuneInnerDeps['rune']>(undefined),
    queryClient: new BehaviorSubject<RuneInnerDeps['queryClient']>(undefined),
    embeddingApi: new BehaviorSubject<Option<EmbeddingApi>>(undefined),
    signingClient: new BehaviorSubject<RuneInnerDeps['signingClient']>(undefined),
    senseApi: new BehaviorSubject<RuneInnerDeps['senseApi']>(undefined),
    address: new BehaviorSubject<RuneInnerDeps['address']>(undefined),
    dbApi: new BehaviorSubject<RuneInnerDeps['dbApi']>(undefined),
  };

  let abortController: Option<AbortController>;

  const defferedDependency = (name: keyof RuneInnerDeps): Promise<RuneInnerDeps[typeof name]> => {
    return new Promise((resolve) => {
      const item$ = subjectDeps[name] as BehaviorSubject<RuneInnerDeps[typeof name]>;
      if (item$.getValue()) {
        resolve(item$.getValue());
      }

      item$
        .pipe(
          first((value) => !!value) // Automatically unsubscribes after the first valid value
          // tap((v) => console.log('------defferedDependency', name, v))
        )
        .subscribe((value) => {
          resolve(value);
        });
    });
  };

  CyberClient.connect(RPC_URL).then((client) => {
    subjectDeps.queryClient?.next(client);
  });

  const setInnerDeps = (externalDeps: Partial<RuneInnerDeps>) => {
    Object.keys(externalDeps)
      .filter((name) => externalDeps[name as keyof RuneInnerDeps] !== undefined)
      .forEach((name) => {
        const item = externalDeps[name as keyof RuneInnerDeps];
        subjectDeps[name as keyof RuneInnerDeps].next(item);
      });
  };

  const graphSearch = async (query: string, page = 0) => {
    const queryClient = (await defferedDependency('queryClient')) as CyberClient;

    const keywordHash = await getSearchQuery(query);

    return searchByHash(queryClient, keywordHash, page);
  };

  const getIpfsTextConent = async (cid: string) => {
    const ipfsApi = (await defferedDependency('ipfsApi')) as IpfsApi;
    return ipfsApi.fetchWithDetails(cid, 'text');
  };

  const evalScriptFromIpfs = async (cid: ParticleCid, funcName: string, params = {}) => {
    try {
      const result = await getIpfsTextConent(cid);
      if (result?.content === undefined) {
        return { action: 'error', message: 'Particle not found' };
      }
      // in case of soul script is mixed with markdown
      // need to extract pure script
      const pureScript = extractRuneScript(result.content);

      const rune = (await defferedDependency('rune')) as RuneEngine;

      return rune.executeFunction(pureScript, funcName, params);
    } catch (e) {
      return { action: 'error', message: e.toString() };
    }
  };

  const executeScriptCallback = async (refId: string, data = {}) => {
    try {
      const rune = (await defferedDependency('rune')) as RuneEngine;
      return rune.executeCallback(refId, data);
    } catch (e) {
      return { action: 'error', message: e.toString() };
    }
  };

  const createAbortController = () => {
    abortController = new AbortController();
    return abortController;
  };

  const abort = () => {
    abortController?.abort();
  };

  const cybApi = {
    createAbortController,
    graphSearch,
    cyberlink: async (from: string, to: string) => {
      const address = subjectDeps.address.getValue();
      if (!address) {
        throw new Error('Connect your wallet first');
      }
      const senseApi = (await defferedDependency('senseApi')) as SenseApi;
      const signingClient = (await defferedDependency('signingClient')) as SigningCyberClient;

      return sendCyberlink(address, from, to, {
        senseApi,
        signingClient,
      });
    },
    getPassportByNickname: async (nickname: string) => {
      const queryClient = await defferedDependency('queryClient');
      const passport = await getPassportByNickname(queryClient, nickname);

      return passport;
    },
    searcByEmbedding: async (text: string, count = 10) => {
      const embeddingApi = (await defferedDependency('embeddingApi')) as EmbeddingApi;
      await defferedDependency('dbApi');
      // console.log('----searcByEmbedding', text);
      return embeddingApi.searchByEmbedding(text, count);
    },
    evalScriptFromIpfs,
    getIpfsTextConent,
    addContenToIpfs: async (content: string) => {
      const ipfsApi = (await defferedDependency('ipfsApi')) as IpfsApi;

      return ipfsApi.addContent(content);
    },
    executeScriptCallback,
  };

  return { setInnerDeps, cybApi, abort };
};

const runeDeps = createRuneDeps();

export type RuneDeps = typeof runeDeps;

// export type EngineDeps = ReturnType<typeof createRuneDeps>;

export default runeDeps;

Neighbours