() => {
    const abiPiece = {
      constant: false,
      inputs: [
        {
          name: 'foo',
          type: 'uint256',
        },
        {
          name: 'bar',
          type: 'bytes32',
        },
      ],
      name: 'doFooBar',
      outputs: [],
      payable: false,
      type: 'function',
    }

    const documentation = {
      details: 'A cool contract that does cool stuff',
      methods: {
        'doFooBar(uint256,bytes32)': {
          details: 'Does a bit of foo and some bar',
          params: {
            foo: 'A bit of foo',
            bar: 'Some bar',
          },
        },
      },
    }

    it('should get the documentation', () => {
      const res = parse([abiPiece], 'ACoolContract', documentation)

      expect(res.functions.doFooBar[0].documentation).toEqual(documentation.methods['doFooBar(uint256,bytes32)'])
      expect(res.documentation!.details).toEqual(documentation.details)
    })
  }


() => {
      const res = parse([abiPiece], 'ACoolContract', documentation)

      expect(res.functions.doFooBar[0].documentation).toEqual(documentation.methods['doFooBar(uint256,bytes32)'])
      expect(res.documentation!.details).toEqual(documentation.details)
    }


() => {
    it('should work on output-less fallback functions', () => {
      const fallbackAbiFunc: RawAbiDefinition = {
        payable: true,
        stateMutability: 'payable',
        type: 'fallback',
      } as any

      expect(() => parse([fallbackAbiFunc], 'fallback')).not.toThrow()
    })
  }


() => {
      const fallbackAbiFunc: RawAbiDefinition = {
        payable: true,
        stateMutability: 'payable',
        type: 'fallback',
      } as any

      expect(() => parse([fallbackAbiFunc], 'fallback')).not.toThrow()
    }


() => parse([fallbackAbiFunc], 'fallback')


() => {
    it('should work on output-less fallback functions', () => {
      const event: RawAbiDefinition = {
        anonymous: false,
        inputs: [
          {
            indexed: false,
            internalType: 'bytes32',
            name: '',
            type: 'bytes32',
          },
        ],
        name: 'log_bytes32',
        type: 'event',
      } as any

      expect(parse([event], 'sc1')).toEqual({
        constructor: [],
        events: {
          log_bytes32: [
            {
              inputs: [
                {
                  isIndexed: false,
                  name: undefined,
                  type: {
                    size: 32,
                    type: 'bytes',
                    originalType: 'bytes32',
                  },
                },
              ],
              name: 'log_bytes32',
              isAnonymous: false,
            },
          ],
        },
        fallback: undefined,
        documentation: undefined,
        functions: {},
        name: 'Sc1',
        rawName: 'sc1',
      })
    })
  }


() => {
      const event: RawAbiDefinition = {
        anonymous: false,
        inputs: [
          {
            indexed: false,
            internalType: 'bytes32',
            name: '',
            type: 'bytes32',
          },
        ],
        name: 'log_bytes32',
        type: 'event',
      } as any

      expect(parse([event], 'sc1')).toEqual({
        constructor: [],
        events: {
          log_bytes32: [
            {
              inputs: [
                {
                  isIndexed: false,
                  name: undefined,
                  type: {
                    size: 32,
                    type: 'bytes',
                    originalType: 'bytes32',
                  },
                },
              ],
              name: 'log_bytes32',
              isAnonymous: false,
            },
          ],
        },
        fallback: undefined,
        documentation: undefined,
        functions: {},
        name: 'Sc1',
        rawName: 'sc1',
      })
    }


(params = {}) => merge({}, defaults, params)


() => {
  const fnFactory = fixtureFactory<FunctionDeclaration>({
    name: 'constant',
    inputs: [],
    outputs: [{ type: { type: 'string', originalType: 'string' }, name: 'output' }],
    stateMutability: 'view',
  })

  const viewFn = fnFactory()
  const pureFn = fnFactory({ stateMutability: 'pure' })
  const payableFn = fnFactory(fnFactory({ stateMutability: 'payable' }))
  const nonPayableFn = fnFactory(fnFactory({ stateMutability: 'nonpayable' }))
  const viewWithInputs = fnFactory({
    stateMutability: 'pure',
    inputs: [{ type: { type: 'string', originalType: 'string' }, name: 'output' }],
  })

  describe('isConstant', () => {
    it('works', () => {
      expect(isConstant(viewFn)).toEqual(true)
      expect(isConstant(pureFn)).toEqual(true)
      expect(isConstant(payableFn)).toEqual(false)
      expect(isConstant(nonPayableFn)).toEqual(false)
      expect(isConstant(viewWithInputs)).toEqual(false)
    })
  })

  describe('isConstantFn', () => {
    it('works', () => {
      expect(isConstantFn(viewFn)).toEqual(false)
      expect(isConstantFn(pureFn)).toEqual(false)
      expect(isConstantFn(payableFn)).toEqual(false)
      expect(isConstantFn(nonPayableFn)).toEqual(false)
      expect(isConstantFn(viewWithInputs)).toEqual(true)
    })
  })
}


() => {
    it('works', () => {
      expect(isConstant(viewFn)).toEqual(true)
      expect(isConstant(pureFn)).toEqual(true)
      expect(isConstant(payableFn)).toEqual(false)
      expect(isConstant(nonPayableFn)).toEqual(false)
      expect(isConstant(viewWithInputs)).toEqual(false)
    })
  }


() => {
      expect(isConstant(viewFn)).toEqual(true)
      expect(isConstant(pureFn)).toEqual(true)
      expect(isConstant(payableFn)).toEqual(false)
      expect(isConstant(nonPayableFn)).toEqual(false)
      expect(isConstant(viewWithInputs)).toEqual(false)
    }


() => {
    it('works', () => {
      expect(isConstantFn(viewFn)).toEqual(false)
      expect(isConstantFn(pureFn)).toEqual(false)
      expect(isConstantFn(payableFn)).toEqual(false)
      expect(isConstantFn(nonPayableFn)).toEqual(false)
      expect(isConstantFn(viewWithInputs)).toEqual(true)
    })
  }


() => {
      expect(isConstantFn(viewFn)).toEqual(false)
      expect(isConstantFn(pureFn)).toEqual(false)
      expect(isConstantFn(payableFn)).toEqual(false)
      expect(isConstantFn(nonPayableFn)).toEqual(false)
      expect(isConstantFn(viewWithInputs)).toEqual(true)
    }


() => {
  it('create an instance', () => {
    const pipe = new MinmaxPipe();
    expect(pipe).toBeTruthy();
  });
}


() => {
    const pipe = new MinmaxPipe();
    expect(pipe).toBeTruthy();
  }


() => this.server.register({
        plugin: {
          name: options.path,
          register: async (server, registerOpts) => {
            server.route({
              method: 'GET',
              path: options.path,
              handler: async (request, h) => {
                let result;
                for (const handler of this.routes.get(options.path)!) {
                  result = await handler(request.raw.req.headers);
                  if (result) {
                    return result;
                  }
                }
                return h.continue;
              }
            });
          }
        }
      })


async (server, registerOpts) => {
            server.route({
              method: 'GET',
              path: options.path,
              handler: async (request, h) => {
                let result;
                for (const handler of this.routes.get(options.path)!) {
                  result = await handler(request.raw.req.headers);
                  if (result) {
                    return result;
                  }
                }
                return h.continue;
              }
            });
          }


async (request, h) => {
                let result;
                for (const handler of this.routes.get(options.path)!) {
                  result = await handler(request.raw.req.headers);
                  if (result) {
                    return result;
                  }
                }
                return h.continue;
              }


export class Hapi17 implements WebFramework {
  static commonName = `hapi@17`;
  static expectedTopStackFrame = '_executeWrap';
  static versionRange = '>=7.5';

  private server: hapi_17.Server;
  // We can't add two routes on the same path.
  // So instead of registering a new Hapi plugin per path,
  // register only the first time -- passing a function that will iterate
  // through a list of routes keyed under the path.
  private routes = new Map<string, WebFrameworkHandlerFunction[]>();
  private registering = Promise.resolve();

  constructor() {
    const hapi = require('../plugins/fixtures/hapi17') as typeof hapi_17;
    this.server = new hapi.Server();
  }

  addHandler(options: WebFrameworkAddHandlerOptions): void {
    let shouldRegister = false;
    if (!this.routes.has(options.path)) {
      this.routes.set(options.path, [options.fn]);
      shouldRegister = true;
    } else {
      this.routes.get(options.path)!.push(options.fn);
    }

    // Only register a new plugin for the first occurrence of this path.
    if (shouldRegister) {
      this.registering = this.registering.then(() => this.server.register({
        plugin: {
          name: options.path,
          register: async (server, registerOpts) => {
            server.route({
              method: 'GET',
              path: options.path,
              handler: async (request, h) => {
                let result;
                for (const handler of this.routes.get(options.path)!) {
                  result = await handler(request.raw.req.headers);
                  if (result) {
                    return result;
                  }
                }
                return h.continue;
              }
            });
          }
        }
      }));
    }
  }

  async listen(port: number): Promise<number> {
    await this.registering;
    this.server.settings.port = port;
    await this.server.start();
    return Number(this.server.info!.port);
  }

  shutdown(): void {
    this.server.stop();
  }
}


addHandler(options: WebFrameworkAddHandlerOptions): void {
    let shouldRegister = false;
    if (!this.routes.has(options.path)) {
      this.routes.set(options.path, [options.fn]);
      shouldRegister = true;
    } else {
      this.routes.get(options.path)!.push(options.fn);
    }

    // Only register a new plugin for the first occurrence of this path.
    if (shouldRegister) {
      this.registering = this.registering.then(() => this.server.register({
        plugin: {
          name: options.path,
          register: async (server, registerOpts) => {
            server.route({
              method: 'GET',
              path: options.path,
              handler: async (request, h) => {
                let result;
                for (const handler of this.routes.get(options.path)!) {
                  result = await handler(request.raw.req.headers);
                  if (result) {
                    return result;
                  }
                }
                return h.continue;
              }
            });
          }
        }
      }));
    }
  }


async listen(port: number): Promise<number> {
    await this.registering;
    this.server.settings.port = port;
    await this.server.start();
    return Number(this.server.info!.port);
  }


shutdown(): void {
    this.server.stop();
  }


e => undefined


() => this.onSettingsChanged()


e => {
                if (e.affectsConfiguration('python.dataScience', undefined)) {
                    // When config changes happen, recreate our commands.
                    this.dispose();
                }
            }


() => this.isCommandSupported(NotebookCommand, cancelToken)


() => this.getUsableJupyterPythonImpl(cancelToken)


async (cancelToken?: CancellationToken): Promise<boolean> => {
        // See if we can find the command nbconvert
        return Cancellation.race(() => this.isCommandSupported(ConvertCommand), cancelToken);
    }


() => this.isCommandSupported(ConvertCommand)


async (cancelToken?: CancellationToken): Promise<boolean> => {
        // See if we can find the command ipykernel
        return Cancellation.race(() => this.isCommandSupported(KernelCreateCommand), cancelToken);
    }


() => this.isCommandSupported(KernelCreateCommand)


async (cancelToken?: CancellationToken): Promise<boolean> => {
        // See if we can find the command kernelspec
        return Cancellation.race(() => this.isCommandSupported(KernelSpecCommand), cancelToken);
    }


() => this.isCommandSupported(KernelSpecCommand)


async () => {
            let connection: IConnection;
            let kernelSpec: IJupyterKernelSpec | undefined;

            // If our uri is undefined or if it's set to local launch we need to launch a server locally
            if (!uri) {
                const launchResults = await this.startNotebookServer(useDefaultConfig, cancelToken);
                if (launchResults) {
                    connection = launchResults.connection;
                    kernelSpec = launchResults.kernelSpec;
                } else {
                    // Throw a cancellation error if we were canceled.
                    Cancellation.throwIfCanceled(cancelToken);

                    // Otherwise we can't connect
                    throw new Error(localize.DataScience.jupyterNotebookFailure().format(''));
                }
            } else {
                // If we have a URI spec up a connection info for it
                connection = this.createRemoteConnectionInfo(uri);
                kernelSpec = undefined;
            }

            try {
                // If we don't have a kernel spec yet, check using our current connection
                if (!kernelSpec) {
                    kernelSpec = await this.getMatchingKernelSpec(connection, cancelToken);
                }

                // If still not found, log an error (this seems possible for some people, so use the default)
                if (!kernelSpec) {
                    this.logger.logError(localize.DataScience.jupyterKernelSpecNotFound());
                }

                // Try to connect to our jupyter process
                const result = this.serviceContainer.get<INotebookServer>(INotebookServer);
                await result.connect(connection, kernelSpec, cancelToken, workingDir);
                return result;
            } catch (err) {
                // Something else went wrong
                throw new Error(localize.DataScience.jupyterNotebookConnectFailed().format(connection.baseUrl));
            }
        }


async (file: string): Promise<void> => {
        // First we find a way to start a notebook server
        const notebookCommand = await this.findBestCommand('notebook');
        if (!notebookCommand) {
            throw new Error(localize.DataScience.jupyterNotSupported());
        }

        const args: string[] = [`--NotebookApp.file_to_run=${file}`];

        // Don't wait for the exec to finish and don't dispose. It's up to the user to kill the process
        notebookCommand.exec(args, { throwOnStdErr: false, encoding: 'utf8' }).ignoreErrors();
    }


async (file: string, template: string): Promise<string> => {
        // First we find a way to start a nbconvert
        const convert = await this.findBestCommand(ConvertCommand);
        if (!convert) {
            throw new Error(localize.DataScience.jupyterNbConvertNotSupported());
        }

        // Wait for the nbconvert to finish
        const result = await convert.exec([file, '--to', 'python', '--stdout', '--template', template], { throwOnStdErr: false, encoding: 'utf8' });
        if (result.stderr) {
            // Stderr on nbconvert doesn't indicate failure. Just log the result
            this.logger.logInformation(result.stderr);
        }
        return result.stdout;
    }


() => this.sessionManager.getActiveKernelSpecs(connection)


() => this.enumerateSpecs(cancelToken)


(uri: string): IConnection => {
        let url: URL;
        try {
            url = new URL(uri);
        } catch (err) {
            // This should already have been parsed when set, so just throw if it's not right here
            throw err;
        }

        return {
            baseUrl: `${url.protocol}//${url.host}${url.pathname}`,
            token: `${url.searchParams.get('token')}`,
            localLaunch: false,
            dispose: noop
        };
    }


() => {
                    launchResult.dispose();
                }


async (cancelToken?: CancellationToken): Promise<PythonInterpreter | undefined> => {
        // This should be the best interpreter for notebooks
        const found = await this.findBestCommand(NotebookCommand, cancelToken);
        if (found) {
            return found.interpreter();
        }

        return undefined;
    }


async (cancelToken?: CancellationToken): Promise<JupyterServerInfo[] | undefined> => {
        // We have a small python file here that we will execute to get the server info from all running Jupyter instances
        const bestInterpreter = await this.getUsableJupyterPython(cancelToken);
        if (bestInterpreter) {
            const newOptions: SpawnOptions = { mergeStdOutErr: true, token: cancelToken };
            newOptions.env = await this.fixupCondaEnv(newOptions.env, bestInterpreter);
            const processService = await this.processServiceFactory.create();
            const file = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'datascience', 'getServerInfo.py');
            const serverInfoString = await processService.exec(bestInterpreter.path, [file], newOptions);

            let serverInfos: JupyterServerInfo[];
            try {
                // Parse out our results, return undefined if we can't suss it out
                serverInfos = JSON.parse(serverInfoString.stdout.trim()) as JupyterServerInfo[];
            } catch (err) {
                return undefined;
            }
            return serverInfos;
        }

        return undefined;
    }


async () => {
                        if (!diskPath) {
                            return;
                        }
                        try {
                            await fs.remove(path.dirname(diskPath));
                        } catch {
                            noop();
                        }
                    }


async (specName: string, cancelToken?: CancellationToken): Promise<string | undefined> => {
        // Enumerate all specs and get path for the match
        const specs = await this.enumerateSpecs(cancelToken);
        const match = specs.find(s => {
            const js = s as JupyterKernelSpec;
            return js && js.name === specName;
        }) as JupyterKernelSpec;
        return match ? match.specFile : undefined;
    }


s => {
            const js = s as JupyterKernelSpec;
            return js && js.name === specName;
        }


async () => {
                // Try ten times. Process may still be up and running.
                // We don't want to do async as async dispose means it may never finish and then we don't
                // delete
                let count = 0;
                while (count < 10) {
                    try {
                        await fs.remove(resultDir);
                        count = 10;
                    } catch {
                        count += 1;
                    }
                }
            }


async (command: string, cancelToken?: CancellationToken): Promise<boolean> => {
        // See if we can find the command
        try {
            const result = await this.findBestCommand(command, cancelToken);
            return result !== undefined;
        } catch (err) {
            this.logger.logWarning(err);
            return false;
        }
    }


async (inputEnv: NodeJS.ProcessEnv, interpreter: PythonInterpreter): Promise<any> => {
        if (!inputEnv) {
            inputEnv = process.env;
        }
        if (interpreter && interpreter.type === InterpreterType.Conda) {
            return this.condaService.getActivatedCondaEnvironment(interpreter, inputEnv);
        }

        return inputEnv;
    }


async (info: PythonInterpreter | undefined, cancelToken?: CancellationToken): Promise<boolean> => {
        if (info) {
            // Enumerate our specs
            const specs = await this.enumerateSpecs(cancelToken);

            // See if any of their paths match
            return specs.findIndex(s => {
                if (info && s && s.path) {
                    return this.fileSystem.arePathsSame(s.path, info.path);
                }
                return false;
            }) >= 0;
        }

        // If no active interpreter, just act like everything is okay as we can't find a new spec anyway
        return true;
    }


s => {
                if (info && s && s.path) {
                    return this.fileSystem.arePathsSame(s.path, info.path);
                }
                return false;
            }


async (enumerator: () => Promise<(IJupyterKernelSpec | undefined)[]>): Promise<IJupyterKernelSpec | undefined> => {
        // Extract our current python information that the user has picked.
        // We'll match against this.
        const info = await this.interpreterService.getActiveInterpreter();
        let bestScore = 0;
        let bestSpec: IJupyterKernelSpec | undefined;

        // Then enumerate our specs
        const specs = await enumerator();

        // For each get its details as we will likely need them
        const specDetails = await Promise.all(specs.map(async s => {
            if (s && s.path && s.path.length > 0 && await fs.pathExists(s.path)) {
                return this.interpreterService.getInterpreterDetails(s.path);
            }
        }));

        for (let i = 0; specs && i < specs.length; i += 1) {
            const spec = specs[i];
            let score = 0;

            if (spec && spec.path && spec.path.length > 0 && info && spec.path === info.path) {
                // Path match
                score += 10;
            }
            if (spec && spec.language && spec.language.toLocaleLowerCase() === 'python') {
                // Language match
                score += 1;

                // See if the version is the same
                if (info && info.version && specDetails[i]) {
                    const details = specDetails[i];
                    if (details && details.version) {
                        if (details.version.major === info.version.major) {
                            // Major version match
                            score += 4;

                            if (details.version.minor === info.version.minor) {
                                // Minor version match
                                score += 2;

                                if (details.version.patch === info.version.patch) {
                                    // Minor version match
                                    score += 1;
                                }
                            }
                        }
                    }
                } else if (info && info.version && spec && spec.path && spec.path.toLocaleLowerCase() === 'python' && spec.name) {
                    // This should be our current python.

                    // Search for a digit on the end of the name. It should match our major version
                    const match = /\D+(\d+)/.exec(spec.name);
                    if (match && match !== null && match.length > 0) {
                        // See if the version number matches
                        const nameVersion = parseInt(match[0], 10);
                        if (nameVersion && nameVersion === info.version.major) {
                            score += 4;
                        }
                    }
                }
            }

            // Update high score
            if (score > bestScore) {
                bestScore = score;
                bestSpec = spec;
            }
        }

        // If still not set, at least pick the first one
        if (!bestSpec && specs && specs.length > 0) {
            bestSpec = specs[0];
        }

        return bestSpec;
    }


async s => {
            if (s && s.path && s.path.length > 0 && await fs.pathExists(s.path)) {
                return this.interpreterService.getInterpreterDetails(s.path);
            }
        }


async (cancelToken?: CancellationToken): Promise<(IJupyterKernelSpec | undefined)[]> => {
        if (await this.isKernelSpecSupported()) {
            const kernelSpecCommand = await this.findBestCommand(KernelSpecCommand);

            if (kernelSpecCommand) {
                try {
                    // Ask for our current list.
                    const list = await kernelSpecCommand.exec(['list'], { throwOnStdErr: true, encoding: 'utf8' });

                    // This should give us back a key value pair we can parse
                    const lines = list.stdout.splitLines({ trim: false, removeEmptyEntries: true });

                    // Generate all of the promises at once
                    const promises = lines.map(l => this.readSpec(l));

                    // Then let them run concurrently (they are file io)
                    const specs = await Promise.all(promises);
                    return specs.filter(s => s);
                } catch {
                    // This is failing for some folks. In that case return nothing
                    return [];
                }
            }
        }

        return [];
    }


l => this.readSpec(l)


s => s


async (command: string, interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<JupyterCommand | undefined> => {
        // If the module is found on this interpreter, then we found it.
        if (interpreter && await this.doesModuleExist(command, interpreter, cancelToken) && !Cancellation.isCanceled(cancelToken)) {
            // We need a process service to create a command
            const processService = await this.processServicePromise;

            // Our command args are different based on the command. ipykernel is not a jupyter command
            const args = command === KernelCreateCommand ? ['-m', command] : ['-m', 'jupyter', command];

            return new JupyterCommand(interpreter.path, args, processService, interpreter, this.condaService);
        }

        return undefined;
    }


async (pathToCheck: string): Promise<string[]> => {
        try {
            const files = await this.fileSystem.getFiles(pathToCheck);
            return files ? files.filter(s => CheckJupyterRegEx.test(path.basename(s))) : [];
        } catch (err) {
            this.logger.logWarning('Python Extension (fileSystem.getFiles):', err);
        }
        return [] as string[];
    }


s => CheckJupyterRegEx.test(path.basename(s))


async (): Promise<string | undefined> => {
        if (!this.jupyterPath) {
            const paths = this.knownSearchPaths.getSearchPaths();
            for (let i = 0; i < paths.length && !this.jupyterPath; i += 1) {
                const found = await this.lookForJupyterInDirectory(paths[i]);
                if (found.length > 0) {
                    this.jupyterPath = found[0];
                }
            }
        }
        return this.jupyterPath;
    }


async (command: string, cancelToken?: CancellationToken): Promise<JupyterCommand | undefined> => {
        if (await this.doesJupyterCommandExist(command, cancelToken) && !Cancellation.isCanceled(cancelToken)) {
            // Search the known paths for jupyter
            const jupyterPath = await this.searchPathsForJupyter();
            if (jupyterPath) {
                // We need a process service to create a command
                const processService = await this.processServicePromise;
                return new JupyterCommand(jupyterPath, [command], processService, this.interpreterService, this.condaService);
            }
        }
        return undefined;
    }


async (command: string, cancelToken?: CancellationToken): Promise<JupyterCommand | undefined> => {
        // See if we already have this command in list
        if (!this.commands.hasOwnProperty(command)) {
            // Not found, try to find it.

            // First we look in the current interpreter
            const current = await this.interpreterService.getActiveInterpreter();
            let found = current ? await this.findInterpreterCommand(command, current, cancelToken) : undefined;
            if (!found && this.supportsSearchingForCommands()) {
                // Look through all of our interpreters (minus the active one at the same time)
                const all = await this.interpreterService.getInterpreters();
                const promises = all.filter(i => i !== current).map(i => this.findInterpreterCommand(command, i, cancelToken));
                const foundList = await Promise.all(promises);

                // Then go through all of the found ones and pick the closest python match
                if (current && current.version) {
                    let bestScore = -1;
                    for (let i = 0; i < foundList.length; i += 1) {
                        let currentScore = 0;
                        if (foundList[i]) {
                            const interpreter = await foundList[i].interpreter();
                            const version = interpreter.version;
                            if (version) {
                                if (version.major === current.version.major) {
                                    currentScore += 4;
                                    if (version.minor === current.version.minor) {
                                        currentScore += 2;
                                        if (version.patch === current.version.patch) {
                                            currentScore += 1;
                                        }
                                    }
                                }
                            }
                            if (currentScore > bestScore) {
                                found = foundList[i];
                                bestScore = currentScore;
                            }
                        }
                    }
                } else {
                    // Just pick the first one
                    found = foundList.find(f => f !== undefined);
                }
            }

            // If still not found, try looking on the path using jupyter
            if (!found) {
                found = await this.findPathCommand(command, cancelToken);
            }

            // If we found a command, save in our dictionary
            if (found) {
                this.commands[command] = found;
            }
        }

        // Return result
        return this.commands.hasOwnProperty(command) ? this.commands[command] : undefined;
    }


i => i !== current


i => this.findInterpreterCommand(command, i, cancelToken)


f => f !== undefined


async (module: string, interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<boolean> => {
        if (interpreter && interpreter !== null) {
            const newOptions: SpawnOptions = { throwOnStdErr: true, encoding: 'utf8', token: cancelToken };
            newOptions.env = await this.fixupCondaEnv(newOptions.env, interpreter);
            const pythonService = await this.executionFactory.create({ pythonPath: interpreter.path });
            try {
                // Special case for ipykernel
                const actualModule = module === KernelCreateCommand ? module : 'jupyter';
                const args = module === KernelCreateCommand ? ['--version'] : [module, '--version'];

                const result = await pythonService.execModule(actualModule, args, newOptions);
                return !result.stderr;
            } catch (err) {
                this.logger.logWarning(`${err} for ${interpreter.path}`);
                return false;
            }
        } else {
            return false;
        }
    }


async (command?: string, cancelToken?: CancellationToken): Promise<boolean> => {
        const newOptions: SpawnOptions = { throwOnStdErr: true, encoding: 'utf8', token: cancelToken };
        const args = command ? [command, '--version'] : ['--version'];
        const processService = await this.processServicePromise;
        try {
            const result = await processService.exec('jupyter', args, newOptions);
            return !result.stderr;
        } catch (err) {
            this.logger.logWarning(err);
            return false;
        }
    }


// JupyterCommand objects represent some process that can be launched that should be guaranteed to work because it
// was found by testing it previously
class JupyterCommand {
    private exe: string;
    private requiredArgs: string[];
    private launcher: IProcessService;
    private interpreterPromise: Promise<PythonInterpreter | undefined>;
    private condaService: ICondaService;

    constructor(exe: string, args: string[], launcher: IProcessService, interpreter: IInterpreterService | PythonInterpreter, condaService: ICondaService) {
        this.exe = exe;
        this.requiredArgs = args;
        this.launcher = launcher;
        this.condaService = condaService;
        if (interpreter.hasOwnProperty('getInterpreterDetails')) {
            const interpreterService = interpreter as IInterpreterService;
            this.interpreterPromise = interpreterService.getInterpreterDetails(this.exe).catch(e => undefined);
        } else {
            const interpreterDetails = interpreter as PythonInterpreter;
            this.interpreterPromise = Promise.resolve(interpreterDetails);
        }
    }

    public async mainVersion(): Promise<number> {
        const interpreter = await this.interpreterPromise;
        if (interpreter && interpreter.version) {
            return interpreter.version.major;
        } else {
            return this.execVersion();
        }
    }

    public interpreter() : Promise<PythonInterpreter | undefined> {
        return this.interpreterPromise;
    }

    public async execObservable(args: string[], options: SpawnOptions): Promise<ObservableExecutionResult<string>> {
        const newOptions = { ...options };
        newOptions.env = await this.fixupCondaEnv(newOptions.env);
        const newArgs = [...this.requiredArgs, ...args];
        return this.launcher.execObservable(this.exe, newArgs, newOptions);
    }

    public async exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
        const newOptions = { ...options };
        newOptions.env = await this.fixupCondaEnv(newOptions.env);
        const newArgs = [...this.requiredArgs, ...args];
        return this.launcher.exec(this.exe, newArgs, newOptions);
    }

    /**
     * Conda needs specific paths and env vars set to be happy. Call this function to fix up
     * (or created if not present) our environment to run jupyter
     */
    // Base Node.js SpawnOptions uses any for environment, so use that here as well
    // tslint:disable-next-line:no-any
    private async fixupCondaEnv(inputEnv?: NodeJS.ProcessEnv): Promise<any> {
        if (!inputEnv) {
            inputEnv = process.env;
        }
        const interpreter = await this.interpreterPromise;

        if (interpreter && interpreter.type === InterpreterType.Conda) {
            return this.condaService.getActivatedCondaEnvironment(interpreter, inputEnv);
        }

        return inputEnv;
    }

    private async execVersion(): Promise<number> {
        if (this.launcher) {
            const output = await this.launcher.exec(this.exe, ['--version'], { throwOnStdErr: false, encoding: 'utf8' });
            // First number should be our result
            const matches = /.*(\d+).*/m.exec(output.stdout);
            if (matches && matches.length > 1) {
                return parseInt(matches[1], 10);
            }
        }
        return 0;
    }

}


public async mainVersion(): Promise<number> {
        const interpreter = await this.interpreterPromise;
        if (interpreter && interpreter.version) {
            return interpreter.version.major;
        } else {
            return this.execVersion();
        }
    }


public interpreter() : Promise<PythonInterpreter | undefined> {
        return this.interpreterPromise;
    }


public async execObservable(args: string[], options: SpawnOptions): Promise<ObservableExecutionResult<string>> {
        const newOptions = { ...options };
        newOptions.env = await this.fixupCondaEnv(newOptions.env);
        const newArgs = [...this.requiredArgs, ...args];
        return this.launcher.execObservable(this.exe, newArgs, newOptions);
    }


public async exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
        const newOptions = { ...options };
        newOptions.env = await this.fixupCondaEnv(newOptions.env);
        const newArgs = [...this.requiredArgs, ...args];
        return this.launcher.exec(this.exe, newArgs, newOptions);
    }


/**
     * Conda needs specific paths and env vars set to be happy. Call this function to fix up
     * (or created if not present) our environment to run jupyter
     */
// Base Node.js SpawnOptions uses any for environment, so use that here as well
// tslint:disable-next-line:no-any
private async fixupCondaEnv(inputEnv?: NodeJS.ProcessEnv): Promise<any> {
        if (!inputEnv) {
            inputEnv = process.env;
        }
        const interpreter = await this.interpreterPromise;

        if (interpreter && interpreter.type === InterpreterType.Conda) {
            return this.condaService.getActivatedCondaEnvironment(interpreter, inputEnv);
        }

        return inputEnv;
    }


private async execVersion(): Promise<number> {
        if (this.launcher) {
            const output = await this.launcher.exec(this.exe, ['--version'], { throwOnStdErr: false, encoding: 'utf8' });
            // First number should be our result
            const matches = /.*(\d+).*/m.exec(output.stdout);
            if (matches && matches.length > 1) {
                return parseInt(matches[1], 10);
            }
        }
        return 0;
    }


public dispose() {
        // Clear our usableJupyterInterpreter
        this.usablePythonInterpreter = undefined;
        this.commands = {};
    }


public isNotebookSupported(cancelToken?: CancellationToken): Promise<boolean> {
        // See if we can find the command notebook
        return Cancellation.race(() => this.isCommandSupported(NotebookCommand, cancelToken), cancelToken);
    }


public async getUsableJupyterPython(cancelToken?: CancellationToken): Promise<PythonInterpreter | undefined> {
        // Only try to compute this once.
        if (!this.usablePythonInterpreter) {
            this.usablePythonInterpreter = await Cancellation.race(() => this.getUsableJupyterPythonImpl(cancelToken), cancelToken);
        }
        return this.usablePythonInterpreter;
    }


public connectToNotebookServer(uri: string | undefined, useDefaultConfig: boolean, cancelToken?: CancellationToken, workingDir?: string): Promise<INotebookServer | undefined> {
        // Return nothing if we cancel
        return Cancellation.race(async () => {
            let connection: IConnection;
            let kernelSpec: IJupyterKernelSpec | undefined;

            // If our uri is undefined or if it's set to local launch we need to launch a server locally
            if (!uri) {
                const launchResults = await this.startNotebookServer(useDefaultConfig, cancelToken);
                if (launchResults) {
                    connection = launchResults.connection;
                    kernelSpec = launchResults.kernelSpec;
                } else {
                    // Throw a cancellation error if we were canceled.
                    Cancellation.throwIfCanceled(cancelToken);

                    // Otherwise we can't connect
                    throw new Error(localize.DataScience.jupyterNotebookFailure().format(''));
                }
            } else {
                // If we have a URI spec up a connection info for it
                connection = this.createRemoteConnectionInfo(uri);
                kernelSpec = undefined;
            }

            try {
                // If we don't have a kernel spec yet, check using our current connection
                if (!kernelSpec) {
                    kernelSpec = await this.getMatchingKernelSpec(connection, cancelToken);
                }

                // If still not found, log an error (this seems possible for some people, so use the default)
                if (!kernelSpec) {
                    this.logger.logError(localize.DataScience.jupyterKernelSpecNotFound());
                }

                // Try to connect to our jupyter process
                const result = this.serviceContainer.get<INotebookServer>(INotebookServer);
                await result.connect(connection, kernelSpec, cancelToken, workingDir);
                return result;
            } catch (err) {
                // Something else went wrong
                throw new Error(localize.DataScience.jupyterNotebookConnectFailed().format(connection.baseUrl));
            }
        }, cancelToken);
    }


protected async getMatchingKernelSpec(connection?: IConnection, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> {
        // If not using an active connection, check on disk
        if (!connection) {
            // Get our best interpreter. We want its python path
            const bestInterpreter = await this.getUsableJupyterPython(cancelToken);

            // Enumerate our kernel specs that jupyter will know about and see if
            // one of them already matches based on path
            if (bestInterpreter && !await this.hasSpecPathMatch(bestInterpreter, cancelToken)) {

                // Nobody matches on path, so generate a new kernel spec
                if (await this.isKernelCreateSupported(cancelToken)) {
                    await this.addMatchingSpec(bestInterpreter, cancelToken);
                }
            }
        }

        // Now enumerate them again
        const enumerator = connection ? () => this.sessionManager.getActiveKernelSpecs(connection) : () => this.enumerateSpecs(cancelToken);

        // Then find our match
        return this.findSpecMatch(enumerator);
    }


@captureTelemetry(Telemetry.StartJupyter)
    private async startNotebookServer(useDefaultConfig: boolean, cancelToken?: CancellationToken): Promise<{ connection: IConnection; kernelSpec: IJupyterKernelSpec | undefined }> {
        // First we find a way to start a notebook server
        const notebookCommand = await this.findBestCommand(NotebookCommand, cancelToken);
        if (!notebookCommand) {
            throw new Error(localize.DataScience.jupyterNotSupported());
        }

        // Now actually launch it
        try {
            // Generate a temp dir with a unique GUID, both to match up our started server and to easily clean up after
            const tempDir = await this.generateTempDir();
            this.disposableRegistry.push(tempDir);

            // In the temp dir, create an empty config python file. This is the same
            // as starting jupyter with all of the defaults.
            const configFile = useDefaultConfig ? path.join(tempDir.path, 'jupyter_notebook_config.py') : undefined;
            if (configFile) {
                await this.fileSystem.writeFile(configFile, '');
                this.logger.logInformation(`Generating custom default config at ${configFile}`);
            }

            // Create extra args based on if we have a config or not
            const extraArgs: string[] = [];
            if (useDefaultConfig) {
                extraArgs.push(`--config=${configFile}`);
            }
            // Check for the debug environment variable being set. Setting this
            // causes Jupyter to output a lot more information about what it's doing
            // under the covers and can be used to investigate problems with Jupyter.
            if (process.env && process.env.VSCODE_PYTHON_DEBUG_JUPYTER) {
                extraArgs.push('--debug');
            }

            // Use this temp file and config file to generate a list of args for our command
            const args: string[] = [...['--no-browser', `--notebook-dir=${tempDir.path}`], ...extraArgs];

            // Before starting the notebook process, make sure we generate a kernel spec
            const kernelSpec = await this.getMatchingKernelSpec(undefined, cancelToken);

            // Then use this to launch our notebook process.
            const launchResult = await notebookCommand.execObservable(args, { throwOnStdErr: false, encoding: 'utf8', token: cancelToken });

            // Make sure this process gets cleaned up. We might be canceled before the connection finishes.
            if (launchResult && cancelToken) {
                cancelToken.onCancellationRequested(() => {
                    launchResult.dispose();
                });
            }

            // Wait for the connection information on this result
            const connection = await JupyterConnection.waitForConnection(
                tempDir.path, this.getJupyterServerInfo, launchResult, this.serviceContainer, cancelToken);

            return {
                connection: connection,
                kernelSpec: kernelSpec
            };
        } catch (err) {
            if (err instanceof CancellationError) {
                throw err;
            }

            // Something else went wrong
            throw new Error(localize.DataScience.jupyterNotebookFailure().format(err));
        }
    }


private onSettingsChanged() {
        // Do the same thing as dispose so that we regenerate
        // all of our commands
        this.dispose();
    }


private async addMatchingSpec(bestInterpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<void> {
        const displayName = localize.DataScience.historyTitle();
        const ipykernelCommand = await this.findBestCommand(KernelCreateCommand, cancelToken);

        // If this fails, then we just skip this spec
        try {
            // Run the ipykernel install command. This will generate a new kernel spec. However
            // it will be pointing to the python that ran it. We'll fix that up afterwards
            const name = uuid();
            if (ipykernelCommand) {
                const result = await ipykernelCommand.exec(['install', '--user', '--name', name, '--display-name', `'${displayName}'`], { throwOnStdErr: true, encoding: 'utf8', token: cancelToken });

                // Result should have our file name.
                const match = PyKernelOutputRegEx.exec(result.stdout);
                const diskPath = match && match !== null && match.length > 1 ? path.join(match[1], 'kernel.json') : await this.findSpecPath(name);

                // Make sure we delete this file at some point. When we close VS code is probably good. It will also be destroy when
                // the kernel spec goes away
                this.asyncRegistry.push({
                    dispose: async () => {
                        if (!diskPath) {
                            return;
                        }
                        try {
                            await fs.remove(path.dirname(diskPath));
                        } catch {
                            noop();
                        }
                    }
                });

                // If that works, rewrite our active interpreter into the argv
                if (diskPath && bestInterpreter) {
                    if (await fs.pathExists(diskPath)) {
                        const specModel: Kernel.ISpecModel = await fs.readJSON(diskPath);
                        specModel.argv[0] = bestInterpreter.path;
                        await fs.writeJSON(diskPath, specModel, { flag: 'w', encoding: 'utf8' });
                    }
                }
            }
        } catch (err) {
            this.logger.logError(err);
        }
    }


private async generateTempDir(): Promise<TemporaryDirectory> {
        const resultDir = path.join(os.tmpdir(), uuid());
        await this.fileSystem.createDirectory(resultDir);

        return {
            path: resultDir,
            dispose: async () => {
                // Try ten times. Process may still be up and running.
                // We don't want to do async as async dispose means it may never finish and then we don't
                // delete
                let count = 0;
                while (count < 10) {
                    try {
                        await fs.remove(resultDir);
                        count = 10;
                    } catch {
                        count += 1;
                    }
                }
            }
        };
    }


private async readSpec(kernelSpecOutputLine: string) : Promise<JupyterKernelSpec | undefined> {
        const match = KernelSpecOutputRegEx.exec(kernelSpecOutputLine);
        if (match && match !== null && match.length > 2) {
            // Second match should be our path to the kernel spec
            const file = path.join(match[2], 'kernel.json');
            if (await fs.pathExists(file)) {
                // Turn this into a IJupyterKernelSpec
                const model = await fs.readJSON(file, { encoding: 'utf8' });
                model.name = match[1];
                return new JupyterKernelSpec(model, file);
            }
        }

        return undefined;
    }


private supportsSearchingForCommands() : boolean {
        if (this.configuration) {
            const settings = this.configuration.getSettings();
            if (settings) {
                return settings.datascience.searchForJupyter;
            }
        }
        return true;
    }


function handleChange(evt) {
      setValue(evt.target.value);
    }


(props, ref) => {
    const [value, setValue] = useState<string>(
      props.initialAnswer ? (props.initialAnswer as string) : ""
    );

    useImperativeHandle(ref, () => ({
      getAnswer() {
        return {
          questionId: props.question.id,
          answer: value,
        };
      },
    }));

    return <input type="text" value={value} onChange={handleChange} />;

    function handleChange(evt) {
      setValue(evt.target.value);
    }
  }


() => ({
      getAnswer() {
        return {
          questionId: props.question.id,
          answer: value,
        };
      },
    })


getAnswer() {
        return {
          questionId: props.question.id,
          answer: value,
        };
      }


(color: string) => (
  <>
    <circle
      cx="127.99707"


(color: string) => (
  <>
    <path
      d="M187.06231,203.68216l-10.731-10.75225a7.99991,7.99991,0,0,0-3.6318-2.08674l-21.45845-5.63128a8,8,0,0,1-5.884-8.90337l2.38477-16.19606a8,8,0,0,1,4.84416-6.22191l30.45054-12.65666a8,8,0,0,1,8.4696,1.48392l24.89413,22.76769.05166-.118a96.30537,96.30537,0,0,1-29.18668,38.157Z"


(color: string) => (
  <>
    <circle
      cx="127.99805"


(color: string) => (
  <>
    <circle
      cx="127.99902"


(color: string) => (
  <>
    <circle
      cx="128"


(weight: IconWeight, color: string) =>
  renderPathForWeight(weight, color, pathsByWeight)


(props, ref) => <IconBase ref


props => {
    const { size, withHoverEffect, color, margin, ...restProps } = props;
    return (
        <SvgIcon {...{ size, withHoverEffect, color, margin, ...restProps }}>
            <PermContactCalendarIconSvg {...restProps} width="1em" height="1em" />
        </SvgIcon>


// Button component props type
export interface ButtonProps {
  children?: ReactNode;
  disabled?: boolean | null;
  isDark: boolean;
  loading?: boolean;
  label: string;
  // can be changed
  type: 'primary' | 'secondary' | 'default';
  onPress?: () => void;
  customStyles: StyleType;
  // to show some loading state
  // inside the button its self
}


export default class NotFoundPage extends React.Component<INotFoundPageProps, INotFoundPageState> {

  constructor(props: INotFoundPageProps) {
    super(props);
    this.state = {
      name: this.props.history.location.pathname.substring(1, this.props.history.location.pathname.length)
    }
  }
  componentWillMount() {
    // TODO
  }
  componentDidUpdate() {
    // TODO
  }
  componentWillUpdate() {
    // TODO
  }
  render() {
    return (
      <div className="NotFoundPage">
        {this.state.name} Component
      </div>);
  }
}


interface INotFoundPageProps extends RouteComponentProps<{ name: string }> {
  // TODO
}


interface INotFoundPageState {
  name: string
}


componentWillMount() {
    // TODO
  }


componentDidUpdate() {
    // TODO
  }


componentWillUpdate() {
    // TODO
  }


render() {
    return (
      <div className="NotFoundPage">
        {this.state.name} Component
      </div>);
  }


(res: any) => {
            if (res.status_code == 200) {
                this.setState({ list: res.data.list, total: res.data.total, last_page: res.data.last_page, page: this.state.page + 1 })
            } else {
                Taro.showToast({ title: res.message, icon: 'none' })
            }
        }


err => {
            Taro.showToast({ title: '请求失败', icon: 'none' })
        }


(res: any) => {
                if (res.status_code == 200) {
                    that.setState({ list: that.state.list.concat(res.data.list), total: res.data.total, last_page: res.data.last_page, page: that.state.page + 1 })
                } else {
                    Taro.showToast({ title: res.message, icon: 'none' })
                }
            }


err => {
                Taro.showToast({ title: '请求失败', icon: 'none' })
            }


(item: any, index: any) => {
                                    return (
                                        <View className='invitation-item' key={item}>
                                            <View className='invitation-item-uesr'>
                                                <View className='invitation-photo'>
                                                    <Image className="invitation-img" src={item.avatar} />
                                                </View>
                                                <View className='invitation-name'>{item.name}</View>
                                            </View>
                                            <View className='invitation-item-time'>{item.created_at}</View>
                                        </View>


export default class AppreActivity extends Component {
    config = {
        navigationBarTitleText: "我的邀请列表",
        enablePullDownRefresh: false
    };

    state = {
        page: 1,
        total: 0,
        last_page: 0,
        list: [

        ]
    };

    /**
    *第一次请求,替代
    */
    componentDidShow() {
        userRelationShip({ page: 1 }).then((res: any) => {
            if (res.status_code == 200) {
                this.setState({ list: res.data.list, total: res.data.total, last_page: res.data.last_page, page: this.state.page + 1 })
            } else {
                Taro.showToast({ title: res.message, icon: 'none' })
            }
        }).catch(err => {
            Taro.showToast({ title: '请求失败', icon: 'none' })
        })
    }

    /**
    *加载更多，衔接
    */
    onReachBottom() {
        let that = this;
        if (this.state.page <= this.state.last_page) {
            userRelationShip({ page: that.state.page }).then((res: any) => {
                if (res.status_code == 200) {
                    that.setState({ list: that.state.list.concat(res.data.list), total: res.data.total, last_page: res.data.last_page, page: that.state.page + 1 })
                } else {
                    Taro.showToast({ title: res.message, icon: 'none' })
                }
            }).catch(err => {
                Taro.showToast({ title: '请求失败', icon: 'none' })
            })
        }

    }

    render() {
        return (
            <View className="invitation-list">
                <View className="invitation-list-nav">
                    <View className="invitation-title">邀请的用户数量</View>
                    <View className="invitation-num">{this.state.total}</View>
                </View>
                <View className='invitation-title-left-box' style={{ background: this.state.list.length ? '#f3f4f8' : '#fff' }}


/**
    *第一次请求,替代
    */
componentDidShow() {
        userRelationShip({ page: 1 }).then((res: any) => {
            if (res.status_code == 200) {
                this.setState({ list: res.data.list, total: res.data.total, last_page: res.data.last_page, page: this.state.page + 1 })
            } else {
                Taro.showToast({ title: res.message, icon: 'none' })
            }
        }).catch(err => {
            Taro.showToast({ title: '请求失败', icon: 'none' })
        })
    }


/**
    *加载更多，衔接
    */
onReachBottom() {
        let that = this;
        if (this.state.page <= this.state.last_page) {
            userRelationShip({ page: that.state.page }).then((res: any) => {
                if (res.status_code == 200) {
                    that.setState({ list: that.state.list.concat(res.data.list), total: res.data.total, last_page: res.data.last_page, page: that.state.page + 1 })
                } else {
                    Taro.showToast({ title: res.message, icon: 'none' })
                }
            }).catch(err => {
                Taro.showToast({ title: '请求失败', icon: 'none' })
            })
        }

    }


render() {
        return (
            <View className="invitation-list">
                <View className="invitation-list-nav">
                    <View className="invitation-title">邀请的用户数量</View>
                    <View className="invitation-num">{this.state.total}</View>
                </View>
                <View className='invitation-title-left-box' style={{ background: this.state.list.length ? '#f3f4f8' : '#fff' }}


require('@/assets/index/no-data.png')


({ Component, pageProps }: AppProps): JSX.Element => {
    return (
        <>
            <Head>
                <title>Ethereum DAPP Demo</title>
                <meta
                    name="viewport"
                    content="minimum-scale=1, initial-scale=1, width=device-width"
                />
            </Head>
            <Component {...pageProps} />
        </>


() => {
  return (
    <HomeContainer>

Nice, you are logged in

    </HomeContainer>


@Component({
  selector: 'tabs-section',
  template: `
    <demo-section [name]="name" [src]="src" [titleDoc]="titleDoc" [html]="html" [ts]="ts" [doc]="doc">
      <tabs-demo></tabs-demo>
    </demo-section>`
})
export class TabsSectionComponent {
  public name:string = 'Tabs';
  public src:string = 'https://github.com/valor-software/ng2-bootstrap/blob/master/components/tabs';
  public html:string = html;
  public ts:string = ts;
  public titleDoc:string = titleDoc;
  public doc:string = doc;
}


export class CreateUserUseCaseImpl implements ICreateUserUseCase {
  constructor(private readonly userRepository: IUserRepository) {}

  async execute(params: CreateUserParams): Promise<CreateUserResponse> {
    return await this.userRepository.create(params)
  }
}


async execute(params: CreateUserParams): Promise<CreateUserResponse> {
    return await this.userRepository.create(params)
  }


/**
 * Perform operations with the group backend microservice.
 */
@Injectable()
export class GroupService {
    private groups: Group[];

    // Maven fills in these variables from the pom.xml
    private url = '${group.service.url}';

  constructor(private http: HttpClient) {
      this.url = this.url + ((this.url.indexOf('/', this.url.length - 1) === -1) ? '/': '') ;

      if (sessionStorage.jwt == null) {
          console.log('JSON Web Token is not available. Login before you continue.');
      }
  }

  getGroups(userId: string): Observable<Groups> {
      let headers = new HttpHeaders();
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.get<Groups>(this.url + '?userId=' + userId, { headers: headers })
      .map(data => data)
  }

  getGroup(groupId: string): Observable<Group> {
      let headers = new HttpHeaders();
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.get<Group>(this.url + groupId, { headers: headers })
      .map(data => data)
  }

  createGroup(payload: string): Observable<HttpResponse<any>> {
      let headers = new HttpHeaders();
      headers = headers.set('Content-Type', 'application/json');
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.post<HttpResponse<any>>(this.url, payload, { headers: headers })
      .map(data => data);
  }

  updateGroup(groupId: string, payload: string): Observable<HttpResponse<any>> {
      let headers = new HttpHeaders();
      headers = headers.set('Content-Type', 'application/json');
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.put<HttpResponse<any>>(this.url + groupId, payload, { headers: headers })
      .map(data => data);
  }

  deleteGroup(groupId: string): Observable<HttpResponse<any>> {
      let headers = new HttpHeaders();
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.delete<HttpResponse<any>>(this.url + groupId, { headers: headers })
      .map(data => data);
  }
}


getGroups(userId: string): Observable<Groups> {
      let headers = new HttpHeaders();
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.get<Groups>(this.url + '?userId=' + userId, { headers: headers })
      .map(data => data)
  }


getGroup(groupId: string): Observable<Group> {
      let headers = new HttpHeaders();
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.get<Group>(this.url + groupId, { headers: headers })
      .map(data => data)
  }


createGroup(payload: string): Observable<HttpResponse<any>> {
      let headers = new HttpHeaders();
      headers = headers.set('Content-Type', 'application/json');
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.post<HttpResponse<any>>(this.url, payload, { headers: headers })
      .map(data => data);
  }


updateGroup(groupId: string, payload: string): Observable<HttpResponse<any>> {
      let headers = new HttpHeaders();
      headers = headers.set('Content-Type', 'application/json');
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.put<HttpResponse<any>>(this.url + groupId, payload, { headers: headers })
      .map(data => data);
  }


deleteGroup(groupId: string): Observable<HttpResponse<any>> {
      let headers = new HttpHeaders();
      headers = headers.set('Authorization', sessionStorage.jwt);

      return this.http.delete<HttpResponse<any>>(this.url + groupId, { headers: headers })
      .map(data => data);
  }


(rows: Packet[] | Flow[], property: PacketTableSortProperty | FlowTableSortProperty, direction: SortDirection) => {
    let sortedRows = [...rows];
    sortedRows.sort((a, b) => {
        let propertyA = a[property] || '';
        let propertyB = b[property] || '';
        if (propertyA < propertyB) {
            return direction === 'asc' ? -1 : 1;
        }
        if (propertyA > propertyB) {
            return direction === 'asc' ? 1 : -1;
        }
        return 0;
    });
    return sortedRows;
}


(a, b) => {
        let propertyA = a[property] || '';
        let propertyB = b[property] || '';
        if (propertyA < propertyB) {
            return direction === 'asc' ? -1 : 1;
        }
        if (propertyA > propertyB) {
            return direction === 'asc' ? 1 : -1;
        }
        return 0;
    }


export type SortDirection = 'asc' | 'desc';


() => this.onPageIndexChange(lastIndex)


() => {
      this.locale = this.i18n.getLocaleData('Pagination');
      this.cdr.markForCheck();
    }


total => {
      this.onTotalChange(total);
    }


bp => {
        if (this.nzResponsive) {
          this.size = bp === NzBreakpointEnum.xs ? 'small' : 'default';
          this.cdr.markForCheck();
        }
      }


@Component({
  selector: 'nz-pagination',
  exportAs: 'nzPagination',
  preserveWhitespaces: false,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <ng-container *ngIf="showPagination">
      <ng-container *ngIf="nzSimple; else defaultPagination.template">
        <ng-template [ngTemplateOutlet]="simplePagination.template"></ng-template>
      </ng-container>
    </ng-container>
    <nz-pagination-simple
      #simplePagination
      [disabled]="nzDisabled"
      [itemRender]="nzItemRender"
      [locale]="locale"
      [pageSize]="nzPageSize"
      [total]="nzTotal"
      [pageIndex]="nzPageIndex"
      (pageIndexChange)="onPageIndexChange($event)"
    ></nz-pagination-simple>
    <nz-pagination-default
      #defaultPagination
      [nzSize]="size"
      [itemRender]="nzItemRender"
      [showTotal]="nzShowTotal"
      [disabled]="nzDisabled"
      [locale]="locale"
      [showSizeChanger]="nzShowSizeChanger"
      [showQuickJumper]="nzShowQuickJumper"
      [total]="nzTotal"
      [pageIndex]="nzPageIndex"
      [pageSize]="nzPageSize"
      [pageSizeOptions]="nzPageSizeOptions"
      (pageIndexChange)="onPageIndexChange($event)"
      (pageSizeChange)="onPageSizeChange($event)"
    ></nz-pagination-default>
  `,
  host: {
    '[class.ant-pagination]': 'true',
    '[class.ant-pagination-simple]': 'nzSimple',
    '[class.ant-pagination-disabled]': 'nzDisabled',
    '[class.mini]': `!nzSimple && size === 'small'`
  }
})
export class NzPaginationComponent implements OnInit, OnDestroy, OnChanges {
  static ngAcceptInputType_nzDisabled: BooleanInput;
  static ngAcceptInputType_nzShowSizeChanger: BooleanInput;
  static ngAcceptInputType_nzHideOnSinglePage: BooleanInput;
  static ngAcceptInputType_nzShowQuickJumper: BooleanInput;
  static ngAcceptInputType_nzSimple: BooleanInput;
  static ngAcceptInputType_nzResponsive: BooleanInput;
  static ngAcceptInputType_nzTotal: NumberInput;
  static ngAcceptInputType_nzPageIndex: NumberInput;
  static ngAcceptInputType_nzPageSize: NumberInput;

  @Output() readonly nzPageSizeChange: EventEmitter<number> = new EventEmitter();
  @Output() readonly nzPageIndexChange: EventEmitter<number> = new EventEmitter();
  @Input() nzShowTotal: TemplateRef<{ $implicit: number; range: [number, number] }> | null = null;
  @Input() nzSize: 'default' | 'small' = 'default';
  @Input() nzPageSizeOptions = [10, 20, 30, 40];
  @Input() nzItemRender: TemplateRef<PaginationItemRenderContext> | null = null;
  @Input() @InputBoolean() nzDisabled = false;
  @Input() @InputBoolean() nzShowSizeChanger = false;
  @Input() @InputBoolean() nzHideOnSinglePage = false;
  @Input() @InputBoolean() nzShowQuickJumper = false;
  @Input() @InputBoolean() nzSimple = false;
  @Input() @InputBoolean() nzResponsive = false;
  @Input() @InputNumber() nzTotal = 0;
  @Input() @InputNumber() nzPageIndex = 1;
  @Input() @InputNumber() nzPageSize = 10;

  showPagination = true;
  locale!: NzPaginationI18nInterface;
  size: 'default' | 'small' = 'default';

  private destroy$ = new Subject<void>();
  private total$ = new ReplaySubject<number>(1);

  validatePageIndex(value: number, lastIndex: number): number {
    if (value > lastIndex) {
      return lastIndex;
    } else if (value < 1) {
      return 1;
    } else {
      return value;
    }
  }

  onPageIndexChange(index: number): void {
    const lastIndex = this.getLastIndex(this.nzTotal, this.nzPageSize);
    const validIndex = this.validatePageIndex(index, lastIndex);
    if (validIndex !== this.nzPageIndex && !this.nzDisabled) {
      this.nzPageIndex = validIndex;
      this.nzPageIndexChange.emit(this.nzPageIndex);
    }
  }

  onPageSizeChange(size: number): void {
    this.nzPageSize = size;
    this.nzPageSizeChange.emit(size);
    const lastIndex = this.getLastIndex(this.nzTotal, this.nzPageSize);
    if (this.nzPageIndex > lastIndex) {
      this.onPageIndexChange(lastIndex);
    }
  }

  onTotalChange(total: number): void {
    const lastIndex = this.getLastIndex(total, this.nzPageSize);
    if (this.nzPageIndex > lastIndex) {
      Promise.resolve().then(() => this.onPageIndexChange(lastIndex));
    }
  }

  getLastIndex(total: number, pageSize: number): number {
    return Math.ceil(total / pageSize);
  }

  constructor(private i18n: NzI18nService, private cdr: ChangeDetectorRef, private breakpointService: NzBreakpointService) {}

  ngOnInit(): void {
    this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
      this.locale = this.i18n.getLocaleData('Pagination');
      this.cdr.markForCheck();
    });

    this.total$.pipe(takeUntil(this.destroy$)).subscribe(total => {
      this.onTotalChange(total);
    });

    this.breakpointService
      .subscribe(gridResponsiveMap)
      .pipe(takeUntil(this.destroy$))
      .subscribe(bp => {
        if (this.nzResponsive) {
          this.size = bp === NzBreakpointEnum.xs ? 'small' : 'default';
          this.cdr.markForCheck();
        }
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  ngOnChanges(changes: SimpleChanges): void {
    const { nzHideOnSinglePage, nzTotal, nzPageSize, nzSize } = changes;
    if (nzTotal) {
      this.total$.next(this.nzTotal);
    }
    if (nzHideOnSinglePage || nzTotal || nzPageSize) {
      this.showPagination = (this.nzHideOnSinglePage && this.nzTotal > this.nzPageSize) || (this.nzTotal > 0 && !this.nzHideOnSinglePage);
    }

    if (nzSize) {
      this.size = nzSize.currentValue;
    }
  }
}


validatePageIndex(value: number, lastIndex: number): number {
    if (value > lastIndex) {
      return lastIndex;
    } else if (value < 1) {
      return 1;
    } else {
      return value;
    }
  }


onPageIndexChange(index: number): void {
    const lastIndex = this.getLastIndex(this.nzTotal, this.nzPageSize);
    const validIndex = this.validatePageIndex(index, lastIndex);
    if (validIndex !== this.nzPageIndex && !this.nzDisabled) {
      this.nzPageIndex = validIndex;
      this.nzPageIndexChange.emit(this.nzPageIndex);
    }
  }


onPageSizeChange(size: number): void {
    this.nzPageSize = size;
    this.nzPageSizeChange.emit(size);
    const lastIndex = this.getLastIndex(this.nzTotal, this.nzPageSize);
    if (this.nzPageIndex > lastIndex) {
      this.onPageIndexChange(lastIndex);
    }
  }


onTotalChange(total: number): void {
    const lastIndex = this.getLastIndex(total, this.nzPageSize);
    if (this.nzPageIndex > lastIndex) {
      Promise.resolve().then(() => this.onPageIndexChange(lastIndex));
    }
  }


getLastIndex(total: number, pageSize: number): number {
    return Math.ceil(total / pageSize);
  }


ngOnInit(): void {
    this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
      this.locale = this.i18n.getLocaleData('Pagination');
      this.cdr.markForCheck();
    });

    this.total$.pipe(takeUntil(this.destroy$)).subscribe(total => {
      this.onTotalChange(total);
    });

    this.breakpointService
      .subscribe(gridResponsiveMap)
      .pipe(takeUntil(this.destroy$))
      .subscribe(bp => {
        if (this.nzResponsive) {
          this.size = bp === NzBreakpointEnum.xs ? 'small' : 'default';
          this.cdr.markForCheck();
        }
      });
  }


ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }


ngOnChanges(changes: SimpleChanges): void {
    const { nzHideOnSinglePage, nzTotal, nzPageSize, nzSize } = changes;
    if (nzTotal) {
      this.total$.next(this.nzTotal);
    }
    if (nzHideOnSinglePage || nzTotal || nzPageSize) {
      this.showPagination = (this.nzHideOnSinglePage && this.nzTotal > this.nzPageSize) || (this.nzTotal > 0 && !this.nzHideOnSinglePage);
    }

    if (nzSize) {
      this.size = nzSize.currentValue;
    }
  }


export default class ResearcherGloves extends Item {

  static get name(): string {
    return 'Researcher\'s Gloves';
  }

  static get isPassive(): boolean {
    return false;
  }

  static get desc(): string {
    return 'A heavy sword.';
  }

  static get buyPrice(): number {
    return 30.0;
  }

  static get sellPrice(): number {
    return 15.0;
  }

  static get isAvailable(): boolean {
    return true;
  }

  static get itemType(): ItemType {
    return 'equipment';
  }

  static toSymbol() {
    return 'ResearcherGloves';
  }

  public static equip(kaiScript: AvatarKaiScript) {
    kaiScript.addBonus.intelligence += 30;
    kaiScript.addBonus.knowledge -= 30;
  }
  static get accessibility() {
    return consts.StoreAccessbility.Rank2;
  }
}


static toSymbol() {
    return 'ResearcherGloves';
  }


public static equip(kaiScript: AvatarKaiScript) {
    kaiScript.addBonus.intelligence += 30;
    kaiScript.addBonus.knowledge -= 30;
  }


(evt) => {
            this.fullscreen = !!document.fullscreenElement;
        }


val => {
                    this.setVolume(val / 100);
                }


_ => this.showControls = true


_ => this.showControls = false


_ => {
                    this._ended = false;
                    this.playing = true;
                    this.starting = false;
                    this.interactionRequired = false;
                    this._progressSub = timer(0, 50)
                    .pipe(
                        takeWhile(_ => !this._ended)
                    ).subscribe(
                        _ => {
                            this.currTime = this.getCurrentTime();
                            this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
                        }
                    )
                }


_ => !this._ended


_ => {
                            this.currTime = this.getCurrentTime();
                            this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
                        }


err => this.interactionRequired = true


_ => {
                this.currTime = this.getCurrentTime();
                this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
            }


@Component({
    selector: 'video-player',
    templateUrl: './template.html',
    styleUrls: ['./styles.scss']
})
export class VideoPlayerComponent extends SubscriberComponent {

    @ViewChild('player') set player(p: ElementRef<HTMLVideoElement>) {
        if (p && p.nativeElement) {
            this._player = p.nativeElement;
        }
    }
    @ViewChild('playercontainer') set playercontainer(pc: ElementRef<HTMLDivElement>) {
        if (pc && pc.nativeElement) {
            this._playerContainer = pc.nativeElement;
        }
    }

    @Input('video') set video(v: VideoInfo) {
        this._video = v;
        this._ready = false;
        this.ready.emit(false);
        if (v) {
            if (v.Url) {
                this.loading = true;
                this.videoSrc = this._sanitizer.bypassSecurityTrustResourceUrl(v.Url);
            }
            if (v['Poster']) {
                this.videoPoster = this._sanitizer.bypassSecurityTrustResourceUrl(v['Poster']);
            }
        }
    }

    @Input('isHost') isHost: boolean = false;
    @Input('startTime') startTime: string | Date | number;

    @Output('ready') ready: EventEmitter<boolean> = new EventEmitter<boolean>();
    @Output('resumed') resumed: EventEmitter<boolean> = new EventEmitter<boolean>();
    @Output('ended') ended: EventEmitter<boolean> = new EventEmitter<boolean>();
    @Output('hostStart') hostStart: EventEmitter<VideoInfo> = new EventEmitter<VideoInfo>();
    @Output('hostStop') hostStop: EventEmitter<VideoInfo> = new EventEmitter<VideoInfo>();
    @Output('hostSelect') hostSelect: EventEmitter<VideoInfo> = new EventEmitter<VideoInfo>();

    volumeControl = new FormControl(100);

    videoSrc: SafeResourceUrl;
    videoPoster: SafeResourceUrl;

    muted: boolean = false;
    premuteVolume: number = 100;

    currTime: number = 0;
    length: number = 0; // duration, but shadows pipe name
    percentPlayed: number = 0;

    starting: boolean;
    stopping: boolean;
    playing: boolean;
    loading: boolean;

    private _ended: boolean = false;

    get video(): VideoInfo {
        return this._video;
    }

    fullscreen: boolean = false;
    showControls: boolean = false;
    interactionRequired: boolean = false;

    private _player: HTMLVideoElement;
    private _playerContainer: HTMLDivElement;
    private _video: VideoInfo;

    private _progressSub: Subscription;

    private _ready: boolean = false;

    private _mouseSubject: Subject<any> = new Subject<any>();

    constructor(
        private _sanitizer: DomSanitizer
    ) {
        super();

        document.onfullscreenchange = (evt) => {
            this.fullscreen = !!document.fullscreenElement;
        }

        this.addSubscription(
            this.volumeControl.valueChanges
            .pipe(
                debounceTime(100),
                distinctUntilChanged()
            )
            .subscribe(
                val => {
                    this.setVolume(val / 100);
                }
            )
        );

        this.addSubscription(
            this._mouseSubject
            .pipe(
                tap(_ => this.showControls = true),
                debounceTime(1500)
            ).subscribe(
                _ => this.showControls = false
            )
        );
    }

    testSeek(event: MouseEvent) {
        console.log(event);
    }

    mouseMove(evt) {
        this._mouseSubject.next(evt);
    }

    getCurrentTime(): number {
        if (this._player) {
            return this._player.currentTime;
        }
    }

    seekTo(seconds: number) {
        if (seconds < 0) {
            return;
        }
        if (this._player) {
            if (this._player.duration > seconds) {
                this._player.currentTime = seconds;
            }
        }
    }

    play(startTime?: string | number | Date) {
        if (this._player && this.videoSrc && this._ready) {
            const now = new Date().valueOf();
            const start = startTime || this.startTime;
            if (!start) {
                return; // no known start time
            }
            const seekTime = Math.floor(Math.max(0, now - new Date(start).valueOf())/1000);
            if (seekTime > this.video.Length) {
                return; // movie is over
            }
            this.seekTo(seekTime);
            this._player.play()
            .then(
                _ => {
                    this._ended = false;
                    this.playing = true;
                    this.starting = false;
                    this.interactionRequired = false;
                    this._progressSub = timer(0, 50)
                    .pipe(
                        takeWhile(_ => !this._ended)
                    ).subscribe(
                        _ => {
                            this.currTime = this.getCurrentTime();
                            this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
                        }
                    )
                },
                err => this.interactionRequired = true
            );
        }
    }

    stop() {
        if (this._player) {
            this._player.pause();
            this._player.currentTime = 0;
            this.currTime = 0;
            this.percentPlayed = 0;
            this._ended = true;
            this.playing = false;
            this.stopping = false;
            this._cancelProgress();
        }
    }

    toggleMute() {
        this.muted = !this.muted;
    }

    setVolume(volume: number) {
        this._player.volume = volume;
    }

    goFullScreen() {
        if (!this._player || !this._playerContainer) {
            console.log('no player found');
            return;
        }
        if (document.fullscreenEnabled) {
            if (!!document.fullscreenElement) {
                document.exitFullscreen();
            } else {
                this._playerContainer.requestFullscreen();
            }
        }
    }

    videoReady() {
        this._ready = true;
        this.ready.emit(true);
        this.loading = false;

        this.length = this._player.duration;
    }

    videoEnded() {
        this.playing = false;
        this._ended = true;
        this.percentPlayed = 100;
        this.ended.emit(true);
    }

    playerWaiting() {
        this.resumed.emit(false);
        this.playing = false;
    }

    playbackResumed() {
        this.resumed.emit(true);
        this._cancelProgress();
        this._progressSub = timer(0, 50)
        .pipe(
            takeWhile(_ => !this._ended)
        ).subscribe(
            _ => {
                this.currTime = this.getCurrentTime();
                this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
            }
        )
    }

    // Host-only controls
    startVideo() {
        this.starting = true;
        this.hostStart.emit(this.video);
    }

    stopVideo() {
        this.stopping = true;
        this.hostStop.emit(this.video);
    }

    selectVideo() {
        this.hostSelect.emit(this.video);
    }

    private _cancelProgress() {
        if (this._progressSub) {
            try {
                this._progressSub.unsubscribe();
                this._progressSub = null;
            } catch (e) {
                // do nothing
            }
        }
    }

}


testSeek(event: MouseEvent) {
        console.log(event);
    }


mouseMove(evt) {
        this._mouseSubject.next(evt);
    }


getCurrentTime(): number {
        if (this._player) {
            return this._player.currentTime;
        }
    }


seekTo(seconds: number) {
        if (seconds < 0) {
            return;
        }
        if (this._player) {
            if (this._player.duration > seconds) {
                this._player.currentTime = seconds;
            }
        }
    }


play(startTime?: string | number | Date) {
        if (this._player && this.videoSrc && this._ready) {
            const now = new Date().valueOf();
            const start = startTime || this.startTime;
            if (!start) {
                return; // no known start time
            }
            const seekTime = Math.floor(Math.max(0, now - new Date(start).valueOf())/1000);
            if (seekTime > this.video.Length) {
                return; // movie is over
            }
            this.seekTo(seekTime);
            this._player.play()
            .then(
                _ => {
                    this._ended = false;
                    this.playing = true;
                    this.starting = false;
                    this.interactionRequired = false;
                    this._progressSub = timer(0, 50)
                    .pipe(
                        takeWhile(_ => !this._ended)
                    ).subscribe(
                        _ => {
                            this.currTime = this.getCurrentTime();
                            this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
                        }
                    )
                },
                err => this.interactionRequired = true
            );
        }
    }


stop() {
        if (this._player) {
            this._player.pause();
            this._player.currentTime = 0;
            this.currTime = 0;
            this.percentPlayed = 0;
            this._ended = true;
            this.playing = false;
            this.stopping = false;
            this._cancelProgress();
        }
    }


toggleMute() {
        this.muted = !this.muted;
    }


setVolume(volume: number) {
        this._player.volume = volume;
    }


goFullScreen() {
        if (!this._player || !this._playerContainer) {
            console.log('no player found');
            return;
        }
        if (document.fullscreenEnabled) {
            if (!!document.fullscreenElement) {
                document.exitFullscreen();
            } else {
                this._playerContainer.requestFullscreen();
            }
        }
    }


videoReady() {
        this._ready = true;
        this.ready.emit(true);
        this.loading = false;

        this.length = this._player.duration;
    }


videoEnded() {
        this.playing = false;
        this._ended = true;
        this.percentPlayed = 100;
        this.ended.emit(true);
    }


playerWaiting() {
        this.resumed.emit(false);
        this.playing = false;
    }


playbackResumed() {
        this.resumed.emit(true);
        this._cancelProgress();
        this._progressSub = timer(0, 50)
        .pipe(
            takeWhile(_ => !this._ended)
        ).subscribe(
            _ => {
                this.currTime = this.getCurrentTime();
                this.percentPlayed = Math.floor((this.currTime / this.length) * 100);
            }
        )
    }


// Host-only controls
startVideo() {
        this.starting = true;
        this.hostStart.emit(this.video);
    }


stopVideo() {
        this.stopping = true;
        this.hostStop.emit(this.video);
    }


selectVideo() {
        this.hostSelect.emit(this.video);
    }


private _cancelProgress() {
        if (this._progressSub) {
            try {
                this._progressSub.unsubscribe();
                this._progressSub = null;
            } catch (e) {
                // do nothing
            }
        }
    }


/**
 * @description This plugin generates React Apollo components and HOC with TypeScript typings.
 *
 * It extends the basic TypeScript plugins: `@graphql-codegen/typescript`, `@graphql-codegen/typescript-operations` - and thus shares a similar configuration.
 */
export interface ReactApolloRawPluginConfig extends RawClientSideBasePluginConfig {
  /**
   * @description Customize the output by enabling/disabling the generated Component (deprecated since Apollo-Client v3). For more details: https://www.apollographql.com/docs/react/api/react/components/
   * @default false
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withComponent: true
   * ```
   */
  withComponent?: boolean;
  /**
   * @description Customize the output by enabling/disabling the HOC (deprecated since Apollo-Client v3). For more details: https://www.apollographql.com/docs/react/api/react/hoc/
   * @default false
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withHOC: true
   * ```
   */
  withHOC?: boolean;
  /**
   * @description Customized the output by enabling/disabling the generated React Hooks. For more details: https://www.apollographql.com/docs/react/api/react/hooks/
   * @default true
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withHooks: true
   * ```
   */
  withHooks?: boolean;
  /**
   * @description Customized the output by enabling/disabling the generated mutation function signature.
   * @default true
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withMutationFn: true
   * ```
   */
  withMutationFn?: boolean;
  /**
   * @description Enable generating a function to be used with refetchQueries
   * @default false
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withRefetchFn: false
   * ```
   */
  withRefetchFn?: boolean;
  /**
   * @description Customize the package where apollo-react common lib is loaded from.
   * @default "@apollo/react-common"
   */
  apolloReactCommonImportFrom?: string;
  /**
   * @description Customize the package where apollo-react component lib is loaded from.
   * @default "@apollo/react-components"
   */
  apolloReactComponentsImportFrom?: string;
  /**
   * @description Customize the package where apollo-react HOC lib is loaded from.
   * @default "@apollo/react-hoc"
   */
  apolloReactHocImportFrom?: string;
  /**
   * @description Customize the package where apollo-react hooks lib is loaded from.
   * @default "@apollo/react-hooks"
   */
  apolloReactHooksImportFrom?: string;
  /**
   * @description You can specify a suffix that gets attached to the name of the generated component.
   * @default Component
   */
  componentSuffix?: string;
  /**
   * @description Sets the version of react-apollo.
   * If you are using the old (deprecated) package of `react-apollo`, please set this configuration to `2`.
   * If you are using Apollo-Client v3, please set this to `3`.
   * @default 3
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    reactApolloVersion: 2
   * ```
   */
  reactApolloVersion?: 2 | 3;
  /**
   * @description Customized the output by enabling/disabling the generated result type.
   * @default true
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withResultType: true
   * ```
   */
  withResultType?: boolean;
  /**
   * @description Customized the output by enabling/disabling the generated mutation option type.
   * @default true
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    withMutationOptionsType: true
   * ```
   */
  withMutationOptionsType?: boolean;
  /**
   * @description Allows you to enable/disable the generation of docblocks in generated code.
   * Some IDE's (like VSCode) add extra inline information with docblocks, you can disable this feature if your preferred IDE does not.
   * @default true
   *
   * @exampleMarkdown
   * ```yml
   * generates:
   * path/to/file.ts:
   *  plugins:
   *    - typescript
   *    - typescript-operations
   *    - typescript-react-apollo
   *  config:
   *    addDocBlocks: true
   * ```
   */
  addDocBlocks?: boolean;

  defaultBaseOptions?: { [key: string]: string };

  hooksSuffix?: string;
}


(response) => response.status === 200


(client) => client.health()


(client) => client.executeScript(script, funcs, hasMutation, opts)


export abstract class PixieAPIClientAbstract {
  static readonly DEFAULT_OPTIONS: Required<PixieAPIClientOptions>;

  readonly options: Required<PixieAPIClientOptions>;

  abstract health(cluster: string | ClusterConfig): Observable<Status>;

  abstract executeScript(
    cluster: string | ClusterConfig,
    script: string,
    opts: ExecuteScriptOptions,
    funcs?: VizierQueryFunc[],
  ): Observable<ExecutionStateUpdate>;

  abstract isAuthenticated(): Promise<boolean>;
}


/**
 * API client library for [Pixie](https://px.dev).
 * See the [documentation](https://docs.px.dev/reference/api/overview/) for a complete reference.
 */
export class PixieAPIClient extends PixieAPIClientAbstract {
  /**
   * By default, a new Pixie API Client assumes a matching origin between the UI and API, that a direct connection is
   * sufficient on the current network, and that authentication is being checked elsewhere.
   */
  static readonly DEFAULT_OPTIONS: Required<PixieAPIClientOptions> = Object.freeze({
    apiKey: '',
    uri: `${globalThis?.location?.origin || 'https://work.withpixie.ai'}/api`,
    onUnauthorized: () => {},
    authToken: '',
  });

  private gqlClient: CloudClient;

  private clusterConnections: Map<string, VizierGRPCClient>;

  /**
   * Builds an API client to speak to Pixie's gRPC and GraphQL backends. The former connects to specific clusters;
   * the latter connects to Pixie Cloud.
   *
   * @param options Set these to change the client's behavior or to listen for authorization errors.
   */
  static async create(
    options: PixieAPIClientOptions,
  ): Promise<PixieAPIClient> {
    const client = new PixieAPIClient({ ...PixieAPIClient.DEFAULT_OPTIONS, ...options });
    return client.init();
  }

  private constructor(
    readonly options: Required<PixieAPIClientOptions>,
  ) {
    super();
  }

  private async init(): Promise<PixieAPIClient> {
    this.gqlClient = new CloudClient(this.options);
    await this.gqlClient.getGraphQLPersist();
    this.clusterConnections = new Map();

    return this;
  }

  // Note: this doesn't check if the client already exists, and clobbers any existing client.
  private async createVizierClient(cluster: ClusterConfig) {
    const { ipAddress, token } = await this.gqlClient.getClusterConnection(cluster.id, true);
    const client = new VizierGRPCClient(
      cluster.passthroughClusterAddress ?? ipAddress,
      // If in embed mode, we should always use the auth token with bearer auth.
      this.options.authToken ? this.options.authToken : token,
      cluster.id,
      (this.options.authToken ? false : cluster.attachCredentials ?? false),
    );

    // Note that this doesn't currently clean up clients that haven't been used in a while, so a particularly long
    // user session could hold onto a large number of stale connections. A simple page refresh drops them all.
    // If this becomes a problem, limit the number of clients and rotate out those that haven't been used in a while.
    this.clusterConnections.set(cluster.id, client);
    return client;
  }

  private async getClusterClient(cluster: string | ClusterConfig) {
    let id: string;
    let passthroughClusterAddress: string;
    let attachCredentials = false;

    if (typeof cluster === 'string') {
      id = cluster;
    } else {
      ({ id, passthroughClusterAddress, attachCredentials } = cluster);
    }

    return this.clusterConnections.has(id)
      ? Promise.resolve(this.clusterConnections.get(id))
      : this.createVizierClient({ id, passthroughClusterAddress, attachCredentials });
  }

  // TODO(nick): Once the authentication model settles down, make this easier to use outside of the browser.
  /**
   * Checks whether the current cookies include a valid authentication token.
   * Checks by querying a purpose-built endpoint, to be certain the user really is authenticated.
   */
  isAuthenticated(): Promise<boolean> {
    return fetch(`${this.options.uri}/authorized`,
      {
        headers: {
          ...{
            'x-csrf': GetCSRFCookie(),
          },
          ...(this.options.authToken
            ? { authorization: `Bearer ${this.options.authToken}`, 'X-Use-Bearer': 'true' } : {}),
        },
      }).then((response) => response.status === 200);
  }

  /**
   * Creates a stream that listens for the health of the cluster and the API client's connection to it.
   * This is an Observable, so don't forget to unsubscribe when you're done with it.
   * @param cluster Which cluster to use. Either just its ID, or a full config. If that cluster has previously been
   *        connected in this session, that connection will be reused without changing its configuration.
   */
  health(cluster: string | ClusterConfig): Observable<Status> {
    return from(this.getClusterClient(cluster))
      .pipe(switchMap((client) => client.health()));
  }

  /**
   * Asks a connected cluster to run a PxL script. Returns an event stream that updates at each stage of execution.
   *
   * A typical event stream might look something like this:
   * - start
   * - metadata
   * - mutation info (if the script uses tracepoints)
   * - data
   * - stats
   *
   * @param cluster Which cluster to use. Either just its ID, or a full config. If that cluster has previously been
   *        connected in this session, that connection will be reused without changing its configuration.
   * @param script The source code of the script to be compiled and executed; whitespace and all.
   * @param opts Any extra options, such as encryption.
   * @param funcs Descriptions of which functions in the script to run, and what to do with their output.
   */
  executeScript(
    cluster: string | ClusterConfig,
    script: string,
    opts: ExecuteScriptOptions,
    funcs: VizierQueryFunc[] = [],
  ): Observable<ExecutionStateUpdate> {
    const hasMutation = containsMutation(script);
    return from(this.getClusterClient(cluster))
      .pipe(switchMap((client) => client.executeScript(script, funcs, hasMutation, opts)));
  }

  /**
   * Implementation detail for adapters.
   * Do not use directly unless writing such an adapter.
   *
   * Provides the internal CloudClient, which has a graphQL property.
   * That property is an ApolloClient, with which GraphQL queries can be run directly.
   *
   * @internal
   */
  getCloudClient(): CloudClient {
    return this.gqlClient;
  }
}


/**
 * When calling `PixieAPIClient.create`, this specifies which clusters to connect to, and any special configuration for
 * each of those connections as needed.
 */
export interface ClusterConfig {
  id: string;
  /**
   * If provided, this overrides the endpoint for connections to the GRPC API on a cluster.
   * This includes the protocol, the host, and optionally the port. For example, `https://pixie.example.com:1234`.
   */
  passthroughClusterAddress?: string | undefined;
  /**
   * If true, passes an HTTP Authorization header as part of each request to gRPC services.
   */
  attachCredentials?: boolean;
}


abstract health(cluster: string | ClusterConfig): Observable<Status>;


abstract executeScript(
    cluster: string | ClusterConfig,
    script: string,
    opts: ExecuteScriptOptions,
    funcs?: VizierQueryFunc[],
  ): Observable<ExecutionStateUpdate>;


abstract isAuthenticated(): Promise<boolean>;


/**
   * Builds an API client to speak to Pixie's gRPC and GraphQL backends. The former connects to specific clusters;
   * the latter connects to Pixie Cloud.
   *
   * @param options Set these to change the client's behavior or to listen for authorization errors.
   */
static async create(
    options: PixieAPIClientOptions,
  ): Promise<PixieAPIClient> {
    const client = new PixieAPIClient({ ...PixieAPIClient.DEFAULT_OPTIONS, ...options });
    return client.init();
  }


private async init(): Promise<PixieAPIClient> {
    this.gqlClient = new CloudClient(this.options);
    await this.gqlClient.getGraphQLPersist();
    this.clusterConnections = new Map();

    return this;
  }