static register (integration) {
    if (typeof (integration.name) !== 'string') {
      console.log('Missing .name', integration);
      return;
    }

    if (typeof (integration.itypes) !== 'object') {
      console.log('Missing .itypes object', integration);
      return;
    }

    // Can disable a integration globally
    const disabled = integration.getConfig('disabled', false);
    if (disabled === true || disabled === 'true') {
      console.log(integration.name, 'disabled');
      return;
    }

    if (integration.configName !== undefined && integration.section !== undefined) {
      console.log('Can not have both configName and section set', integration.name, integration.configName, integration.section);
      return;
    }

    integration.cacheable ??= true;
    integration.noStats ??= false;
    integration.order ??= 10000;

    integration.stats = {
      total: 0,
      cacheLookup: 0,
      cacheFound: 0,
      cacheGood: 0,
      cacheRecentAvgMS: 0.0,
      directLookup: 0,
      directFound: 0,
      directGood: 0,
      directError: 0,
      directRecentAvgMS: 0.0
    };

    // cacheTime order we check:
    //   cacheTimeout in integration section
    //   cacheTimeout in cont3xt section
    //   cacheTimeout in integration code
    //   60 minutes
    integration.cacheTimeout = ArkimeUtil.parseTimeStr(integration.getConfig('cacheTimeout', ArkimeConfig.get('cacheTimeout', integration.cacheTimeout ?? '1h'))) * 1000;

    // cachePolicy
    integration.cachePolicy = integration.getConfig('cachePolicy', ArkimeConfig.get('cachePolicy', integration.cachePolicy ?? 'shared'));
    switch (integration.cachePolicy) {
    case 'none':
      integration.cacheable = false;
      integration.sharedCache = false;
      break;
    case 'user':
      integration.sharedCache = false;
      break;
    case 'shared':
      integration.sharedCache = true;
      break;
    default:
      console.log('Unknown cache policy', integration);
      return;
    }

    if (ArkimeConfig.debug > 0) {
      console.log(`REGISTER ${integration.name} cacheTimeout:${integration.cacheTimeout / 1000}s cacheable:${integration.cacheable} sharedCache:${integration.sharedCache} order:${integration.order} itypes:${Object.keys(integration.itypes)}`);
    }

    integration.normalizeCard();
    integration.normalizeTidbits();
    // console.log(integration.name, JSON.stringify(integration.card, false, 2));

    Integration.#integrationsByName[integration.name] = integration;
    Integration.#integrations.all.push(integration);
    for (const itype in integration.itypes) {
      Integration.#integrations[itype].push(integration);
    }

    integration.viewRoles = integration.getConfigArray('viewRoles');

    integration.locked = integration.getConfig('locked', false);

    if (integration.card) { // overwrite the default searchUrls with the integration's
      const configSearchUrls = integration.getConfig('searchUrls', undefined);
      if (configSearchUrls) {
        integration.card.searchUrls = JSON.parse(configSearchUrls);
      }
    }
  }
Register an integration implementation
@param {string} integration.name The name of the integration
@param {object} integration.itypes An object of itypes to functions to call
@param {boolean} integration.cacheable=true Should results be cache
@param {boolean} integration.noStats=false Should we not save stats
@param {number} integration.order=10000 What order should this integration be shown