(): void => console.log(
`engines-lookup v${version}

A tool to check what engines are advised by the dependencies

Usage: engines-lookup [options] [path]

If a path isn't provided, the program will use "."

Options:
-t, --table
    Shows a table of package's name and advised engines. Default: false

-s, --sort <engine>
    Sorts the table entries by the engine specified. The first entry will
    always be the project at the path. Default: "node"

-a, --add
    Adds the result to the project's package.json file. Default: false

-v, --version
    Displays the current version of engines-lookup

-h, --help
    Shows this help message`)


export function useOnCommentAddedSubscription<TData = OnCommentAddedSubscription>(
  options: Omit<Urql.UseSubscriptionArgs<OnCommentAddedSubscriptionVariables>, 'query'> = {},
  handler?: Urql.SubscriptionHandler<OnCommentAddedSubscription, TData>
) {
  return Urql.useSubscription<OnCommentAddedSubscription, TData, OnCommentAddedSubscriptionVariables>(
    { query: OnCommentAddedDocument, ...options },
    handler
  );
}


export function useCommentQuery(options: Omit<Urql.UseQueryArgs<CommentQueryVariables>, 'query'> = {}) {
  return Urql.useQuery<CommentQuery>({ query: CommentDocument, ...options });
}


export function useCurrentUserForProfileQuery(
  options: Omit<Urql.UseQueryArgs<CurrentUserForProfileQueryVariables>, 'query'> = {}
) {
  return Urql.useQuery<CurrentUserForProfileQuery>({ query: CurrentUserForProfileDocument, ...options });
}


export function useFeedQuery(options: Omit<Urql.UseQueryArgs<FeedQueryVariables>, 'query'> = {}) {
  return Urql.useQuery<FeedQuery>({ query: FeedDocument, ...options });
}


export function useSubmitRepositoryMutation() {
  return Urql.useMutation<SubmitRepositoryMutation, SubmitRepositoryMutationVariables>(SubmitRepositoryDocument);
}


export function useSubmitCommentMutation() {
  return Urql.useMutation<SubmitCommentMutation, SubmitCommentMutationVariables>(SubmitCommentDocument);
}


export function useVoteMutation() {
  return Urql.useMutation<VoteMutation, VoteMutationVariables>(VoteDocument);
}


/** A list of options for the sort order of the feed */
export enum FeedType {
  /** Sort by a combination of freshness and score, using Reddit's algorithm */
  Hot = 'HOT',
  /** Newest entries first */
  New = 'NEW',
  /** Highest score entries first */
  Top = 'TOP',
}


/** The type of vote to record, when submitting a vote */
export enum VoteType {
  Cancel = 'CANCEL',
  Down = 'DOWN',
  Up = 'UP',
}


export type Maybe<T> = T | null;


export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };


export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };


export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };


export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;


/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
};


/** A comment about an entry, submitted by a user */
export type Comment = {
  __typename?: 'Comment';
  /** The text of the comment */
  content: Scalars['String'];
  /** A timestamp of when the comment was posted */
  createdAt: Scalars['Float'];
  /** The SQL ID of this entry */
  id: Scalars['Int'];
  /** The GitHub user who posted the comment */
  postedBy: User;
  /** The repository which this comment is about */
  repoName: Scalars['String'];
};


/** Information about a GitHub repository submitted to GitHunt */
export type Entry = {
  __typename?: 'Entry';
  /** The number of comments posted about this repository */
  commentCount: Scalars['Int'];
  /** Comments posted about this repository */
  comments: Array<Maybe<Comment>>;
  /** A timestamp of when the entry was submitted */
  createdAt: Scalars['Float'];
  /** The hot score of this repository */
  hotScore: Scalars['Float'];
  /** The SQL ID of this entry */
  id: Scalars['Int'];
  /** The GitHub user who submitted this entry */
  postedBy: User;
  /** Information about the repository from GitHub */
  repository: Repository;
  /** The score of this repository, upvotes - downvotes */
  score: Scalars['Int'];
  /** XXX to be changed */
  vote: Vote;
};


/** Information about a GitHub repository submitted to GitHunt */
export type EntryCommentsArgs = {
  limit?: Maybe<Scalars['Int']>;
  offset?: Maybe<Scalars['Int']>;
};


export type Mutation = {
  __typename?: 'Mutation';
  /** Comment on a repository, returns the new comment */
  submitComment?: Maybe<Comment>;
  /** Submit a new repository, returns the new submission */
  submitRepository?: Maybe<Entry>;
  /** Vote on a repository submission, returns the submission that was voted on */
  vote?: Maybe<Entry>;
};


export type MutationSubmitCommentArgs = {
  commentContent: Scalars['String'];
  repoFullName: Scalars['String'];
};


export type MutationSubmitRepositoryArgs = {
  repoFullName: Scalars['String'];
};


export type MutationVoteArgs = {
  repoFullName: Scalars['String'];
  type: VoteType;
};


export type Query = {
  __typename?: 'Query';
  /** Return the currently logged in user, or null if nobody is logged in */
  currentUser?: Maybe<User>;
  /** A single entry */
  entry?: Maybe<Entry>;
  /** A feed of repository submissions */
  feed?: Maybe<Array<Maybe<Entry>>>;
};


export type QueryEntryArgs = {
  repoFullName: Scalars['String'];
};


export type QueryFeedArgs = {
  limit?: Maybe<Scalars['Int']>;
  offset?: Maybe<Scalars['Int']>;
  type: FeedType;
};


/**
 * A repository object from the GitHub API. This uses the exact field names returned by the
 * GitHub API for simplicity, even though the convention for GraphQL is usually to camel case.
 */
export type Repository = {
  __typename?: 'Repository';
  /** The description of the repository */
  description?: Maybe<Scalars['String']>;
  /** The full name of the repository with the username, e.g. apollostack/GitHunt-API */
  full_name: Scalars['String'];
  /** The link to the repository on GitHub */
  html_url: Scalars['String'];
  /** Just the name of the repository, e.g. GitHunt-API */
  name: Scalars['String'];
  /** The number of open issues on this repository on GitHub */
  open_issues_count?: Maybe<Scalars['Int']>;
  /** The owner of this repository on GitHub, e.g. apollostack */
  owner?: Maybe<User>;
  /** The number of people who have starred this repository on GitHub */
  stargazers_count: Scalars['Int'];
};


export type Subscription = {
  __typename?: 'Subscription';
  /** Subscription fires on every comment added */
  commentAdded?: Maybe<Comment>;
};


export type SubscriptionCommentAddedArgs = {
  repoFullName: Scalars['String'];
};


/** A user object from the GitHub API. This uses the exact field names returned from the GitHub API. */
export type User = {
  __typename?: 'User';
  /** The URL to a directly embeddable image for this user's avatar */
  avatar_url: Scalars['String'];
  /** The URL of this user's GitHub page */
  html_url: Scalars['String'];
  /** The name of the user, e.g. apollostack */
  login: Scalars['String'];
};


/** XXX to be removed */
export type Vote = {
  __typename?: 'Vote';
  vote_value: Scalars['Int'];
};


export type OnCommentAddedSubscriptionVariables = Exact<{
  repoFullName: Scalars['String'];
}>;


export type OnCommentAddedSubscription = {
  __typename?: 'Subscription';
  commentAdded?: Maybe<{
    __typename?: 'Comment';
    id: number;
    createdAt: number;
    content: string;
    postedBy: { __typename?: 'User'; login: string; html_url: string };
  }>;
};


export type CommentQueryVariables = Exact<{
  repoFullName: Scalars['String'];
  limit?: Maybe<Scalars['Int']>;
  offset?: Maybe<Scalars['Int']>;
}>;


export type CommentQuery = {
  __typename?: 'Query';
  currentUser?: Maybe<{ __typename?: 'User'; login: string; html_url: string }>;
  entry?: Maybe<{
    __typename?: 'Entry';
    id: number;
    createdAt: number;
    commentCount: number;
    postedBy: { __typename?: 'User'; login: string; html_url: string };
    comments: Array<
      Maybe<{
        __typename?: 'Comment';
        id: number;
        createdAt: number;
        content: string;
        postedBy: { __typename?: 'User'; login: string; html_url: string };
      }>
    >;
    repository: {
      __typename?: 'Repository';
      description?: Maybe<string>;
      open_issues_count?: Maybe<number>;
      stargazers_count: number;
      full_name: string;
      html_url: string;
    };
  }>;
};


export type CommentsPageCommentFragment = {
  __typename?: 'Comment';
  id: number;
  createdAt: number;
  content: string;
  postedBy: { __typename?: 'User'; login: string; html_url: string };
};


export type CurrentUserForProfileQueryVariables = Exact<{ [key: string]: never }>;


export type CurrentUserForProfileQuery = {
  __typename?: 'Query';
  currentUser?: Maybe<{ __typename?: 'User'; login: string; avatar_url: string }>;
};


export type FeedEntryFragment = {
  __typename?: 'Entry';
  id: number;
  commentCount: number;
  score: number;
  createdAt: number;
  repository: {
    __typename?: 'Repository';
    full_name: string;
    html_url: string;
    description?: Maybe<string>;
    stargazers_count: number;
    open_issues_count?: Maybe<number>;
    owner?: Maybe<{ __typename?: 'User'; avatar_url: string }>;
  };
  vote: { __typename?: 'Vote'; vote_value: number };
  postedBy: { __typename?: 'User'; html_url: string; login: string };
};


export type FeedQueryVariables = Exact<{
  type: FeedType;
  offset?: Maybe<Scalars['Int']>;
  limit?: Maybe<Scalars['Int']>;
}>;


export type FeedQuery = {
  __typename?: 'Query';
  currentUser?: Maybe<{ __typename?: 'User'; login: string }>;
  feed?: Maybe<
    Array<
      Maybe<{
        __typename?: 'Entry';
        id: number;
        commentCount: number;
        score: number;
        createdAt: number;
        repository: {
          __typename?: 'Repository';
          full_name: string;
          html_url: string;
          description?: Maybe<string>;
          stargazers_count: number;
          open_issues_count?: Maybe<number>;
          owner?: Maybe<{ __typename?: 'User'; avatar_url: string }>;
        };
        vote: { __typename?: 'Vote'; vote_value: number };
        postedBy: { __typename?: 'User'; html_url: string; login: string };
      }>
    >
  >;
};


export type SubmitRepositoryMutationVariables = Exact<{
  repoFullName: Scalars['String'];
}>;


export type SubmitRepositoryMutation = {
  __typename?: 'Mutation';
  submitRepository?: Maybe<{ __typename?: 'Entry'; createdAt: number }>;
};


export type RepoInfoFragment = {
  __typename?: 'Entry';
  createdAt: number;
  repository: {
    __typename?: 'Repository';
    description?: Maybe<string>;
    stargazers_count: number;
    open_issues_count?: Maybe<number>;
  };
  postedBy: { __typename?: 'User'; html_url: string; login: string };
};


export type SubmitCommentMutationVariables = Exact<{
  repoFullName: Scalars['String'];
  commentContent: Scalars['String'];
}>;


export type SubmitCommentMutation = {
  __typename?: 'Mutation';
  submitComment?: Maybe<{
    __typename?: 'Comment';
    id: number;
    createdAt: number;
    content: string;
    postedBy: { __typename?: 'User'; login: string; html_url: string };
  }>;
};


export type VoteButtonsFragment = {
  __typename?: 'Entry';
  score: number;
  vote: { __typename?: 'Vote'; vote_value: number };
};


export type VoteMutationVariables = Exact<{
  repoFullName: Scalars['String'];
  type: VoteType;
}>;


export type VoteMutation = {
  __typename?: 'Mutation';
  vote?: Maybe<{ __typename?: 'Entry'; score: number; id: number; vote: { __typename?: 'Vote'; vote_value: number } }>;
};


export type WithTypename<T extends { __typename?: any }> = { [K in Exclude<keyof T, '__typename'>]?: T[K] } & {
  __typename: NonNullable<T['__typename']>;
};


export type GraphCacheKeysConfig = {
  Comment?: (data: WithTypename<Comment>) => null | string;
  Entry?: (data: WithTypename<Entry>) => null | string;
  Repository?: (data: WithTypename<Repository>) => null | string;
  User?: (data: WithTypename<User>) => null | string;
  Vote?: (data: WithTypename<Vote>) => null | string;
};


export type GraphCacheResolvers = {
  Query?: {
    feed?: GraphCacheResolver<WithTypename<Query>, QueryFeedArgs, Array<WithTypename<Entry> | string>>;
    entry?: GraphCacheResolver<WithTypename<Query>, QueryEntryArgs, WithTypename<Entry> | string>;
    currentUser?: GraphCacheResolver<WithTypename<Query>, Record<string, never>, WithTypename<User> | string>;
  };
  Comment?: {
    id?: GraphCacheResolver<WithTypename<Comment>, Record<string, never>, Scalars['Int'] | string>;
    postedBy?: GraphCacheResolver<WithTypename<Comment>, Record<string, never>, WithTypename<User> | string>;
    createdAt?: GraphCacheResolver<WithTypename<Comment>, Record<string, never>, Scalars['Float'] | string>;
    content?: GraphCacheResolver<WithTypename<Comment>, Record<string, never>, Scalars['String'] | string>;
    repoName?: GraphCacheResolver<WithTypename<Comment>, Record<string, never>, Scalars['String'] | string>;
  };
  Entry?: {
    repository?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, WithTypename<Repository> | string>;
    postedBy?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, WithTypename<User> | string>;
    createdAt?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, Scalars['Float'] | string>;
    score?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, Scalars['Int'] | string>;
    hotScore?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, Scalars['Float'] | string>;
    comments?: GraphCacheResolver<WithTypename<Entry>, EntryCommentsArgs, Array<WithTypename<Comment> | string>>;
    commentCount?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, Scalars['Int'] | string>;
    id?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, Scalars['Int'] | string>;
    vote?: GraphCacheResolver<WithTypename<Entry>, Record<string, never>, WithTypename<Vote> | string>;
  };
  Repository?: {
    name?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, Scalars['String'] | string>;
    full_name?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, Scalars['String'] | string>;
    description?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, Scalars['String'] | string>;
    html_url?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, Scalars['String'] | string>;
    stargazers_count?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, Scalars['Int'] | string>;
    open_issues_count?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, Scalars['Int'] | string>;
    owner?: GraphCacheResolver<WithTypename<Repository>, Record<string, never>, WithTypename<User> | string>;
  };
  User?: {
    login?: GraphCacheResolver<WithTypename<User>, Record<string, never>, Scalars['String'] | string>;
    avatar_url?: GraphCacheResolver<WithTypename<User>, Record<string, never>, Scalars['String'] | string>;
    html_url?: GraphCacheResolver<WithTypename<User>, Record<string, never>, Scalars['String'] | string>;
  };
  Vote?: {
    vote_value?: GraphCacheResolver<WithTypename<Vote>, Record<string, never>, Scalars['Int'] | string>;
  };
};


export type GraphCacheOptimisticUpdaters = {
  submitRepository?: GraphCacheOptimisticMutationResolver<MutationSubmitRepositoryArgs, Maybe<WithTypename<Entry>>>;
  vote?: GraphCacheOptimisticMutationResolver<MutationVoteArgs, Maybe<WithTypename<Entry>>>;
  submitComment?: GraphCacheOptimisticMutationResolver<MutationSubmitCommentArgs, Maybe<WithTypename<Comment>>>;
};


export type GraphCacheUpdaters = {
  Mutation?: {
    submitRepository?: GraphCacheUpdateResolver<
      { submitRepository: Maybe<WithTypename<Entry>> },
      MutationSubmitRepositoryArgs
    >;
    vote?: GraphCacheUpdateResolver<{ vote: Maybe<WithTypename<Entry>> }, MutationVoteArgs>;
    submitComment?: GraphCacheUpdateResolver<
      { submitComment: Maybe<WithTypename<Comment>> },
      MutationSubmitCommentArgs
    >;
  };
  Subscription?: {
    commentAdded?: GraphCacheUpdateResolver<
      { commentAdded: Maybe<WithTypename<Comment>> },
      SubscriptionCommentAddedArgs
    >;
  };
};


export type GraphCacheConfig = {
  schema?: IntrospectionData;
  updates?: GraphCacheUpdaters;
  keys?: GraphCacheKeysConfig;
  optimistic?: GraphCacheOptimisticUpdaters;
  resolvers?: GraphCacheResolvers;
  storage?: GraphCacheStorageAdapter;
};


() => {
  const tempStyleCode = `.test-demo-logo {
    height: 32px;
    margin: 16px;
    background: rgba(255, 255, 255, 0.2);
  }
  .test-demo-site-layout .test-demo-site-layout-background {
    background: #fff;
  }`;
  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: tempStyleCode }}


() => {
  const props = mountEditor(Editor, {
    attrs: {
      hasSider: true,
    },
  });
  return (
    <AntdLayout {...props}>
      <TestContent />
    </AntdLayout>
  );
}


/**
 * Top Root Level Wallet State to control what screen to show
 */
export function RootNavigator (): JSX.Element {
  const {
    wallets,
    isLoaded
  } = useWalletPersistenceContext()

  // To prevent flicker on start of app, while API is not yet called
  if (!isLoaded) {
    return <></>
  }

  if (wallets.length === 0) {
    return <WalletNavigator />
  }

  return (
    <WalletNodeProvider data={wallets[0]}>
      <WalletContextProvider api={WalletAddressIndexPersistence}>
        <PrivacyLock />
        <BottomSheetModalProvider>
          <TransactionAuthorization />
          <AppNavigator />
        </BottomSheetModalProvider>
      </WalletContextProvider>
    </WalletNodeProvider>
  )
}


() => {
  const component = create(<Emg src={ExampleImg} />);
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
}


(value): string => _.get(value, 'requests.storage')


(vmLikeEntity: VMLikeEntityKind): boolean =>
  vmLikeEntity && vmLikeEntity.kind === VirtualMachineModel.kind


(vmLikeEntity: VMLikeEntityKind): VMKind => {
  if (!vmLikeEntity) {
    return null;
  }

  if (isVm(vmLikeEntity)) {
    return vmLikeEntity as VMKind;
  }
  return selectVm(vmLikeEntity as TemplateKind);
}


() => {
    getSubById(this.sub)
      .then(({ data, statusCode }) => {
        if (statusCode === STATUS_SUCCESS) {
          this.setState({
            subData: data as ISubData
          });
        } else {
          this.props.history.push(RouteNames.notFound);
        }
      })
      .catch(() => this.props.history.push(RouteNames.notFound));

    getSubPosts(this.sub, this.state.offset, this.limit).then(
      ({ data, statusCode }) => {
        if (statusCode === STATUS_SUCCESS) {
          this.setState(
            {
              posts: data as Array<IPost>,
              hasFetched: true
            },
            () => {
              if (data?.length < this.limit) {
                this.setState({ hasMoreToFetch: false });
              }
            }
          );
        }
      }
    );
  }


({ data, statusCode }) => {
        if (statusCode === STATUS_SUCCESS) {
          this.setState({
            subData: data as ISubData
          });
        } else {
          this.props.history.push(RouteNames.notFound);
        }
      }


() => this.props.history.push(RouteNames.notFound)


({ data, statusCode }) => {
        if (statusCode === STATUS_SUCCESS) {
          this.setState(
            {
              posts: data as Array<IPost>,
              hasFetched: true
            },
            () => {
              if (data?.length < this.limit) {
                this.setState({ hasMoreToFetch: false });
              }
            }
          );
        }
      }


() => {
              if (data?.length < this.limit) {
                this.setState({ hasMoreToFetch: false });
              }
            }


() => {
    if (this.state.hasMoreToFetch) {
      getSubPosts(this.sub, this.state.offset + this.limit).then(
        ({ data, statusCode }) => {
          if (statusCode === STATUS_SUCCESS) {
            this.setState(
              (state) => ({
                posts: [...state.posts, ...data],
                offset: state.offset + this.limit
              }),
              () => {
                if (data?.length < this.limit) {
                  this.setState((state) => ({ hasMoreToFetch: false }));
                }
              }
            );
          }
        }
      );
    }
  }


({ data, statusCode }) => {
          if (statusCode === STATUS_SUCCESS) {
            this.setState(
              (state) => ({
                posts: [...state.posts, ...data],
                offset: state.offset + this.limit
              }),
              () => {
                if (data?.length < this.limit) {
                  this.setState((state) => ({ hasMoreToFetch: false }));
                }
              }
            );
          }
        }


(state) => ({
                posts: [...state.posts, ...data],
                offset: state.offset + this.limit
              })


() => {
                if (data?.length < this.limit) {
                  this.setState((state) => ({ hasMoreToFetch: false }));
                }
              }


(state) => ({ hasMoreToFetch: false })


() => {
    if (this.props.isAuthenticated) {
      this.props.joinSub(this.sub);
    } else {
      this.props.history.push({
        pathname: RouteNames.login,
        state: { heading: "Login to join subreaddits you like!" }
      });
    }
  }


() => {
    this.props.leaveSub(this.sub);
  }


(item, index) => (
                    <PostCard
                      {...item}


(_, index) => (
                    <PostSkeleton key={index} />


class Subreaddit extends Component<IProps, IState> {
  limit: number;
  sub: string;

  constructor(props: IProps) {
    super(props);

    this.limit = 15;
    this.sub = this.props.match?.params?.id || "";

    this.state = {
      offset: 0,
      posts: [],
      hasMoreToFetch: true,
      hasFetched: false,
      subData: null
    };
  }

  componentDidMount() {
    document.title = getTabTitle(`b/${this.sub}`);
    this.fetchAll();
  }

  componentWillUnmount() {
    document.title = DEFAULT_TITLE;
  }

  fetchAll = () => {
    getSubById(this.sub)
      .then(({ data, statusCode }) => {
        if (statusCode === STATUS_SUCCESS) {
          this.setState({
            subData: data as ISubData
          });
        } else {
          this.props.history.push(RouteNames.notFound);
        }
      })
      .catch(() => this.props.history.push(RouteNames.notFound));

    getSubPosts(this.sub, this.state.offset, this.limit).then(
      ({ data, statusCode }) => {
        if (statusCode === STATUS_SUCCESS) {
          this.setState(
            {
              posts: data as Array<IPost>,
              hasFetched: true
            },
            () => {
              if (data?.length < this.limit) {
                this.setState({ hasMoreToFetch: false });
              }
            }
          );
        }
      }
    );
  };

  fetchMore = () => {
    if (this.state.hasMoreToFetch) {
      getSubPosts(this.sub, this.state.offset + this.limit).then(
        ({ data, statusCode }) => {
          if (statusCode === STATUS_SUCCESS) {
            this.setState(
              (state) => ({
                posts: [...state.posts, ...data],
                offset: state.offset + this.limit
              }),
              () => {
                if (data?.length < this.limit) {
                  this.setState((state) => ({ hasMoreToFetch: false }));
                }
              }
            );
          }
        }
      );
    }
  };

  joinSub = () => {
    if (this.props.isAuthenticated) {
      this.props.joinSub(this.sub);
    } else {
      this.props.history.push({
        pathname: RouteNames.login,
        state: { heading: "Login to join subreaddits you like!" }
      });
    }
  };

  leaveSub = () => {
    this.props.leaveSub(this.sub);
  };

  render() {
    const { posts, hasFetched, subData } = this.state;
    const { classes, user } = this.props;
    const isUserMember = user?.JoinedSubs.includes(this.sub);
    const isUserOwner = subData?.Owner === user?.UserName;

    return (
      <PaginationContainer handlePagination={this.fetchMore}>
        <Paper
          className={classes.bannerContainer}
          style={{ position: "relative" }}


interface SelfProps {
  match: {
    params: {
      [x: string]: string;
    };
  };
  history: any;
}


interface IState {
  offset: number;
  posts: Array<IPost>;
  hasMoreToFetch: boolean;
  hasFetched: boolean;
  subData: ISubData | null;
}


type IProps = SelfProps & IClass & ReduxProps & { width: string };


componentDidMount() {
    document.title = getTabTitle(`b/${this.sub}`);
    this.fetchAll();
  }


componentWillUnmount() {
    document.title = DEFAULT_TITLE;
  }


render() {
    const { posts, hasFetched, subData } = this.state;
    const { classes, user } = this.props;
    const isUserMember = user?.JoinedSubs.includes(this.sub);
    const isUserOwner = subData?.Owner === user?.UserName;

    return (
      <PaginationContainer handlePagination={this.fetchMore}>
        <Paper
          className={classes.bannerContainer}
          style={{ position: "relative" }}


formatNumberNotation(subData?.Users


@NgModule({
    declarations: [
        ServiceComponent,
        ServiceDetailsComponent
    ],
    imports: [
        CommonModule,
        RouterModule.forChild([
            {
                path: '',
                component: ServiceComponent
            },
            {
                path: 'details',
                component: ServiceDetailsComponent
            }
        ]),
    ],
    providers: []
})
export class ServiceModule { }


function getMAIN1string(state): string {
      if (state["MAINQ1-can-we-still-save-the-world"]) return "yesMAIN1";
      else return "noMAIN1";
    }


function getSUBstring(state): string {
      if (
        state["SUBQ1-renewables-zero-carbon"] &&
        state["SUBQ2-livestock-emissions"] &&
        state["SUBQ3-transportation-off-fossil"] &&
        state["SUBQ4-industry-emissions"] &&
        state["SUBQ5-carbon-capture"]
      )
        return "allSUB";

      if (
        state["SUBQ1-renewables-zero-carbon"] ||
        state["SUBQ2-livestock-emissions"] ||
        state["SUBQ3-transportation-off-fossil"] ||
        state["SUBQ4-industry-emissions"] ||
        state["SUBQ5-carbon-capture"]
      )
        return "someSUB";

      return "noSUB";
    }


function getMAIN2(state): string {
      if (state["MAINQ2-can-we-still-save-the-world-again-after-article"])
        return "yesMAIN2";

      return "noMAIN2";
    }


function renderEmbed(embedEl: HTMLElement) {
  const { id } = embedEl.dataset;

  render(<EmbedSwitcher id={id} />, embedEl);
}


() =>
        handler({
          didTimeout: false,
          timeRemaining: () => Math.max(0, 50.0 - (Date.now() - startTime))
        })


() => Math.max(0, 50.0 - (Date.now() - startTime))


({ id }) => {
  const [userInputSerializedState, setUserInputSerializedState] =
    useState("{}");
  const userInputState = JSON.parse(userInputSerializedState);

  useEffect(() => {
    let hasEnded = false;

    rIC(async function poll() {
      const _userInputSerializedState = await idbGet<string>(
        USER_INPUT_STATE_PERSISTENCE_KEY,
        idbKeyvalStore
      );

      if (userInputSerializedState !== _userInputSerializedState) {
        setUserInputSerializedState(_userInputSerializedState || "{}");
      }

      if (!hasEnded) {
        rIC(poll);
      }
    });

    return () => {
      hasEnded = true;
    };
  }, [userInputState]);

  const updateSerializePersistAndSetUserInputState = async fn => {
    const nextUserInputState = fn(userInputState);
    const nextUserInputSerializedState = JSON.stringify(nextUserInputState);

    await idbSet(
      USER_INPUT_STATE_PERSISTENCE_KEY,
      nextUserInputSerializedState,
      idbKeyvalStore
    );
    setUserInputSerializedState(nextUserInputSerializedState);
  };

  const userInputBoxCommonProps = {
    userInputState,
    setUserInputState: updateSerializePersistAndSetUserInputState,
    windowWidth: 0
  };

  let component: JSX.Element | null = null;

  switch (id) {
    case "mainq1":
    case "subq1":
    case "subq2":
    case "subq3":
    case "subq4":
      const key = id.toUpperCase();
      const { color, slug } = QUESTION_CONFIGS[id];
      const optimisticText = dynamicText[`${key}-optimistic`];
      const pessimisticText = dynamicText[`${key}-pessimistic`];

      component = (
        <UserInputBox
          color={color}
          questionKey={`${key}-${slug}`}
          preTitle={<DynText>{dynamicText[`${key}-pre-title`]}</DynText>}
          title={<DynText>{dynamicText[`${key}-title`]}</DynText>}
          buttons={
            id.indexOf("mainq") === 0
              ? [
                  {
                    label: "Of course we can",
                    value: "certain",
                    response: <DynText>{optimisticText}</DynText>
                  },
                  {
                    label: "Yes I think we can",
                    value: "hopeful",
                    response: <DynText>{optimisticText}</DynText>
                  },
                  {
                    label: "Probably not",
                    value: "doubtful",
                    response: <DynText>{pessimisticText}</DynText>
                  },
                  {
                    label: "No way we're screwed",
                    value: "impossible",
                    response: <DynText>{pessimisticText}</DynText>
                  }
                ]
              : [
                  {
                    label: "It can be done",
                    value: "hopeful",
                    response: <DynText>{optimisticText}</DynText>
                  },
                  {
                    label: "This sounds like a stretch",
                    value: "doubtful",
                    response: <DynText>{pessimisticText}</DynText>
                  }
                ]
          }
          {...userInputBoxCommonProps}
        />


() => {
    let hasEnded = false;

    rIC(async function poll() {
      const _userInputSerializedState = await idbGet<string>(
        USER_INPUT_STATE_PERSISTENCE_KEY,
        idbKeyvalStore
      );

      if (userInputSerializedState !== _userInputSerializedState) {
        setUserInputSerializedState(_userInputSerializedState || "{}");
      }

      if (!hasEnded) {
        rIC(poll);
      }
    });

    return () => {
      hasEnded = true;
    };
  }


() => {
      hasEnded = true;
    }


async fn => {
    const nextUserInputState = fn(userInputState);
    const nextUserInputSerializedState = JSON.stringify(nextUserInputState);

    await idbSet(
      USER_INPUT_STATE_PERSISTENCE_KEY,
      nextUserInputSerializedState,
      idbKeyvalStore
    );
    setUserInputSerializedState(nextUserInputSerializedState);
  }


({ userInputState }) => {
  const [userStrings, setUserStrings] = useState({
    renewables: 1,
    transportation: 1,
    carboncapture: 1,
    industry: 1,
    livestock: 1
  });
  const [questionCompleteness, setQuestionCompleteness] = useState("nothing");
  const [convincedState, setConvincedState] = useState("orange");
  const [subQuestionsConvinvedOf, setSubQuestionsConvinvedOf] = useState(0);

  useEffect(() => {
    if (!userInputState) return;

    const nextUserStrings = userStrings;

    if (userInputState["SUBQ1-renewables-zero-carbon"]) {
      switch (userInputState["SUBQ1-renewables-zero-carbon"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.renewables = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.renewables = 1;
          break;
      }
    }

    if (userInputState["SUBQ2-livestock-emissions"]) {
      switch (userInputState["SUBQ2-livestock-emissions"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.livestock = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.livestock = 1;
          break;
      }
    }

    if (userInputState["SUBQ3-transportation-off-fossil"]) {
      switch (userInputState["SUBQ3-transportation-off-fossil"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.transportation = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.transportation = 1;
          break;
      }
    }

    if (userInputState["SUBQ4-industry-emissions"]) {
      switch (userInputState["SUBQ4-industry-emissions"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.industry = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.industry = 1;
          break;
      }
    }

    if (userInputState["SUBQ5-carbon-capture"]) {
      switch (userInputState["SUBQ5-carbon-capture"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.carboncapture = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.carboncapture = 1;
          break;
      }
    }

    setUserStrings(nextUserStrings);

    let localConvincedCount = 0;

    for (const area in nextUserStrings) {
      if (nextUserStrings[area] === 0) localConvincedCount++;
    }

    setSubQuestionsConvinvedOf(localConvincedCount);

    function getMAIN1string(state): string {
      if (state["MAINQ1-can-we-still-save-the-world"]) return "yesMAIN1";
      else return "noMAIN1";
    }

    function getSUBstring(state): string {
      if (
        state["SUBQ1-renewables-zero-carbon"] &&
        state["SUBQ2-livestock-emissions"] &&
        state["SUBQ3-transportation-off-fossil"] &&
        state["SUBQ4-industry-emissions"] &&
        state["SUBQ5-carbon-capture"]
      )
        return "allSUB";

      if (
        state["SUBQ1-renewables-zero-carbon"] ||
        state["SUBQ2-livestock-emissions"] ||
        state["SUBQ3-transportation-off-fossil"] ||
        state["SUBQ4-industry-emissions"] ||
        state["SUBQ5-carbon-capture"]
      )
        return "someSUB";

      return "noSUB";
    }

    function getMAIN2(state): string {
      if (state["MAINQ2-can-we-still-save-the-world-again-after-article"])
        return "yesMAIN2";

      return "noMAIN2";
    }

    const combinedCompletenessStrings =
      getMAIN1string(userInputState) +
      getSUBstring(userInputState) +
      getMAIN2(userInputState);

    setQuestionCompleteness(combinedCompletenessStrings);

    const mainChangeLevels = {
      certain: 4,
      hopeful: 3,
      doubtful: 2,
      impossible: 1
    };

    const main1 = userInputState["MAINQ1-can-we-still-save-the-world"];
    const main2 =
      userInputState["MAINQ2-can-we-still-save-the-world-again-after-article"];

    const mainLevel1 = mainChangeLevels[main1];
    const mainLevel2 = mainChangeLevels[main2];

    if (mainLevel1 && main2) {
      if (mainLevel1 === mainLevel2) setConvincedState("orange");
      if (mainLevel1 > mainLevel2) setConvincedState("red");
      if (mainLevel1 < mainLevel2) setConvincedState("green");
    }
  }, [userInputState]);

  return (
    <div>
      {INTERACTIVE_PANEL_KEYS.map(panelKey => (
        <InteractivePanel
          key={panelKey}
          panelKey={panelKey}
          questionCompleteness={questionCompleteness}
          convincedState={convincedState}
          subQuestionsConvinvedOf={subQuestionsConvinvedOf}
          australiaConvincedOf={0}
          dynamicText={dynamicText}
          dynamicTextLoading={false}
          userInputState={userInputState}
        />
      ))}
    </div>
  );
}


() => {
    if (!userInputState) return;

    const nextUserStrings = userStrings;

    if (userInputState["SUBQ1-renewables-zero-carbon"]) {
      switch (userInputState["SUBQ1-renewables-zero-carbon"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.renewables = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.renewables = 1;
          break;
      }
    }

    if (userInputState["SUBQ2-livestock-emissions"]) {
      switch (userInputState["SUBQ2-livestock-emissions"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.livestock = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.livestock = 1;
          break;
      }
    }

    if (userInputState["SUBQ3-transportation-off-fossil"]) {
      switch (userInputState["SUBQ3-transportation-off-fossil"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.transportation = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.transportation = 1;
          break;
      }
    }

    if (userInputState["SUBQ4-industry-emissions"]) {
      switch (userInputState["SUBQ4-industry-emissions"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.industry = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.industry = 1;
          break;
      }
    }

    if (userInputState["SUBQ5-carbon-capture"]) {
      switch (userInputState["SUBQ5-carbon-capture"]) {
        case "certain":
        case "hopeful":
          nextUserStrings.carboncapture = 0;
          break;
        case "doubtful":
        case "impossible":
          nextUserStrings.carboncapture = 1;
          break;
      }
    }

    setUserStrings(nextUserStrings);

    let localConvincedCount = 0;

    for (const area in nextUserStrings) {
      if (nextUserStrings[area] === 0) localConvincedCount++;
    }

    setSubQuestionsConvinvedOf(localConvincedCount);

    function getMAIN1string(state): string {
      if (state["MAINQ1-can-we-still-save-the-world"]) return "yesMAIN1";
      else return "noMAIN1";
    }

    function getSUBstring(state): string {
      if (
        state["SUBQ1-renewables-zero-carbon"] &&
        state["SUBQ2-livestock-emissions"] &&
        state["SUBQ3-transportation-off-fossil"] &&
        state["SUBQ4-industry-emissions"] &&
        state["SUBQ5-carbon-capture"]
      )
        return "allSUB";

      if (
        state["SUBQ1-renewables-zero-carbon"] ||
        state["SUBQ2-livestock-emissions"] ||
        state["SUBQ3-transportation-off-fossil"] ||
        state["SUBQ4-industry-emissions"] ||
        state["SUBQ5-carbon-capture"]
      )
        return "someSUB";

      return "noSUB";
    }

    function getMAIN2(state): string {
      if (state["MAINQ2-can-we-still-save-the-world-again-after-article"])
        return "yesMAIN2";

      return "noMAIN2";
    }

    const combinedCompletenessStrings =
      getMAIN1string(userInputState) +
      getSUBstring(userInputState) +
      getMAIN2(userInputState);

    setQuestionCompleteness(combinedCompletenessStrings);

    const mainChangeLevels = {
      certain: 4,
      hopeful: 3,
      doubtful: 2,
      impossible: 1
    };

    const main1 = userInputState["MAINQ1-can-we-still-save-the-world"];
    const main2 =
      userInputState["MAINQ2-can-we-still-save-the-world-again-after-article"];

    const mainLevel1 = mainChangeLevels[main1];
    const mainLevel2 = mainChangeLevels[main2];

    if (mainLevel1 && main2) {
      if (mainLevel1 === mainLevel2) setConvincedState("orange");
      if (mainLevel1 > mainLevel2) setConvincedState("red");
      if (mainLevel1 < mainLevel2) setConvincedState("green");
    }
  }


panelKey => (
        <InteractivePanel
          key={panelKey}


() => {
  idbKeyvalStore = createStore(
    `${PROJECT_NAME}-db-${Math.floor(Date.now() / 1e4)}`,
    `${PROJECT_NAME}-store`
  );

  const embedEl = document.getElementById("embed") as HTMLElement;

  if (window.applenews) {
    initAppleNews(embedEl, renderEmbed);
  } else {
    renderEmbed(embedEl);
  }
}


interface EmbedSwitcherProps {
  id?: string;
}


interface UserResultsBoxProps {
  userInputState: Record<string, string>;
}


function __findEndpoint(
    masterClient: OPCUAClientBase,
    endpointUrl: string,
    params: FindEndpointOptions,
    callback: FindEndpointCallback
) {

    debugLog("findEndpoint : endpointUrl = ", endpointUrl);

    const securityMode = params.securityMode;
    const securityPolicy = params.securityPolicy;

    const options: OPCUAClientBaseOptions = {
        connectionStrategy: params.connectionStrategy,

        // endpoint_must_exist: false,

        applicationName: params.applicationName,
        certificateFile: params.certificateFile,
        privateKeyFile: params.privateKeyFile

    };

    const client = new ClientBaseImpl(options);

    let selectedEndpoints: any = null;
    const allEndpoints: any = null;
    const tasks = [

        (innerCallback: ErrorCallback) => {

            // rebind backoff handler
            masterClient.listeners("backoff").forEach(
                (handler: any) => client.on("backoff", handler));

            if (doDebug) {
                client.on("backoff", (retryCount: number, delay: number) => {
                    debugLog("finding Endpoint => reconnecting ",
                        " retry count", retryCount, " next attempt in ", delay / 1000, "seconds");
                });
            }

            client.connect(endpointUrl, (err?: Error) => {
                if (err) {
                    // let's improve the error message with meaningful info
                    err.message = "Fail to connect to server at " + endpointUrl +
                        " to collect certificate server (in findEndpoint) \n" +
                        " (err =" + err.message + ")";
                    debugLog("Fail to connect to server ", endpointUrl, " to collect certificate server");
                }
                return innerCallback(err);
            });
        },

        (innerCallback: ErrorCallback) => {

            client.getEndpoints((err: Error | null, endpoints?: EndpointDescription[]) => {

                if (err) {
                    err.message = "error in getEndpoints \n" + err.message;
                    return innerCallback(err);
                }
                if (!endpoints) {
                    return innerCallback(new Error("Internal Error"));
                }

                endpoints.forEach((endpoint: EndpointDescription) => {
                    if (endpoint.securityMode === securityMode && endpoint.securityPolicyUri === securityPolicy) {
                        selectedEndpoints = endpoint; // found it
                    }
                });

                innerCallback();
            });
        },

        (innerCallback: ErrorCallback) => {
            client.disconnect(innerCallback);
        }
    ];

    async.series(tasks, (err) => {

        if (err) {
            return callback(err);
        }

        if (!selectedEndpoints) {
            callback(new Error("Cannot find an Endpoint matching " +
                " security mode: " + securityMode.toString() +
                " policy: " + securityPolicy.toString()));
        }

        const result = {
            endpoints: allEndpoints,
            selectedEndpoint: selectedEndpoints
        };
        callback(null, result);
    });
}


/**
 * check if certificate is trusted or untrusted
 */
function _verify_serverCertificate(serverCertificate: Certificate, callback: ErrorCallback) {

    // todo:
    //  - use Certificate manager to deal with trusted/ untrusted certificate
    //  - add certificate verification and validity check

    const pkiFolder = process.cwd() + "/pki";

    // istanbul ignore next
    if (!fs.existsSync(pkiFolder)) {
        fs.mkdirSync(pkiFolder);
    }
    const pkiRejectedCertificateFolder = path.join(pkiFolder, "rejected");

    // istanbul ignore next
    if (!fs.existsSync(pkiRejectedCertificateFolder)) {
        fs.mkdirSync(pkiRejectedCertificateFolder);
    }
    const thumbprint = makeSHA1Thumbprint(serverCertificate);

    const certificateFilename = path.join(pkiRejectedCertificateFolder, thumbprint.toString("hex") + ".pem");
    fs.writeFile(certificateFilename, toPem(serverCertificate, "CERTIFICATE"), () => {
        setImmediate(callback);
    });
}


(innerCallback: ErrorCallback) => {

            // rebind backoff handler
            masterClient.listeners("backoff").forEach(
                (handler: any) => client.on("backoff", handler));

            if (doDebug) {
                client.on("backoff", (retryCount: number, delay: number) => {
                    debugLog("finding Endpoint => reconnecting ",
                        " retry count", retryCount, " next attempt in ", delay / 1000, "seconds");
                });
            }

            client.connect(endpointUrl, (err?: Error) => {
                if (err) {
                    // let's improve the error message with meaningful info
                    err.message = "Fail to connect to server at " + endpointUrl +
                        " to collect certificate server (in findEndpoint) \n" +
                        " (err =" + err.message + ")";
                    debugLog("Fail to connect to server ", endpointUrl, " to collect certificate server");
                }
                return innerCallback(err);
            });
        }


(handler: any) => client.on("backoff", handler)


(retryCount: number, delay: number) => {
                    debugLog("finding Endpoint => reconnecting ",
                        " retry count", retryCount, " next attempt in ", delay / 1000, "seconds");
                }


(err?: Error) => {
                if (err) {
                    // let's improve the error message with meaningful info
                    err.message = "Fail to connect to server at " + endpointUrl +
                        " to collect certificate server (in findEndpoint) \n" +
                        " (err =" + err.message + ")";
                    debugLog("Fail to connect to server ", endpointUrl, " to collect certificate server");
                }
                return innerCallback(err);
            }


(innerCallback: ErrorCallback) => {

            client.getEndpoints((err: Error | null, endpoints?: EndpointDescription[]) => {

                if (err) {
                    err.message = "error in getEndpoints \n" + err.message;
                    return innerCallback(err);
                }
                if (!endpoints) {
                    return innerCallback(new Error("Internal Error"));
                }

                endpoints.forEach((endpoint: EndpointDescription) => {
                    if (endpoint.securityMode === securityMode && endpoint.securityPolicyUri === securityPolicy) {
                        selectedEndpoints = endpoint; // found it
                    }
                });

                innerCallback();
            });
        }


(err: Error | null, endpoints?: EndpointDescription[]) => {

                if (err) {
                    err.message = "error in getEndpoints \n" + err.message;
                    return innerCallback(err);
                }
                if (!endpoints) {
                    return innerCallback(new Error("Internal Error"));
                }

                endpoints.forEach((endpoint: EndpointDescription) => {
                    if (endpoint.securityMode === securityMode && endpoint.securityPolicyUri === securityPolicy) {
                        selectedEndpoints = endpoint; // found it
                    }
                });

                innerCallback();
            }


(endpoint: EndpointDescription) => {
                    if (endpoint.securityMode === securityMode && endpoint.securityPolicyUri === securityPolicy) {
                        selectedEndpoints = endpoint; // found it
                    }
                }


(innerCallback: ErrorCallback) => {
            client.disconnect(innerCallback);
        }


(err) => {

        if (err) {
            return callback(err);
        }

        if (!selectedEndpoints) {
            callback(new Error("Cannot find an Endpoint matching " +
                " security mode: " + securityMode.toString() +
                " policy: " + securityPolicy.toString()));
        }

        const result = {
            endpoints: allEndpoints,
            selectedEndpoint: selectedEndpoints
        };
        callback(null, result);
    }


() => {
        setImmediate(callback);
    }


(/*err?: Error*/) => {
            this._secureChannel = null;
            callback();
        }


() => {
            /* empty */
        }


(err: Error, message: string) => {

            errorLog("client = ", this.clientName, message, err.message);

            if (this.reconnectionIsCanceled) {
                this.emit("reconnection_canceled");
                return callback(new Error("Reconnection has been canceled - " + this.clientName));
            }
            // else
            // let retry a little bit later
            this.emit("reconnection_attempt_has_failed", err, message); // send after callback
            setTimeout(_attempt_to_recreate_secure_channel, 100);
        }


() => {

            if (this.reconnectionIsCanceled) {
                this.emit("reconnection_canceled");
                return callback(new Error("Reconnection has been canceled - " + this.clientName));
            }

            this._internal_create_secure_channel(infiniteConnectionRetry, (err?: Error | null) => {

                if (err) {
                    if (err.message.match("ECONNREFUSED")) {
                        return callback(err);
                    }
                    if (err.message.match("Backoff aborted.")) {
                        return failAndRetry(err!, "cannot create secure channel");
                    }
                    if (true || err!.message.match("BadCertificateInvalid")) {
                        errorLog(" _internal_create_secure_channel err = ", err.message);
                        // the server may have shut down the channel because its certificate
                        // has changed ....
                        // let request the server certificate again ....
                        debugLog(chalk.bgWhite.red("ClientBaseImpl: Server Certificate has changed." +
                            " we need to retrieve server certificate again"));

                        return this.fetchServerCertificate(this.endpointUrl, (err1?: Error | null) => {
                            if (err1) {
                                return failAndRetry(err1, "trying to fetch new server certificate");
                            }
                            this._internal_create_secure_channel(infiniteConnectionRetry, (err3?: Error | null) => {
                                if (err3) {
                                    return failAndRetry(err3, "trying to create new channel with new certificate");
                                }
                                callback();
                            });
                        });

                    }
                    debugLog(chalk.bgWhite.red("ClientBaseImpl: cannot reconnect .."));
                    failAndRetry(err!, "cannot create secure channel");

                } else {

                    /**
                     * notify the observers that the reconnection process has been completed
                     * @event after_reconnection
                     * @param err
                     */
                    this.emit("after_reconnection", err); // send after callback
                    assert(this._secureChannel, "expecting a secureChannel here ");
                    // a new channel has be created and a new connection is established
                    debugLog(chalk.bgWhite.red("ClientBaseImpl:  RECONNECTED                !!!"));
                    return callback();
                }
            });
        }


(err?: Error | null) => {

                if (err) {
                    if (err.message.match("ECONNREFUSED")) {
                        return callback(err);
                    }
                    if (err.message.match("Backoff aborted.")) {
                        return failAndRetry(err!, "cannot create secure channel");
                    }
                    if (true || err!.message.match("BadCertificateInvalid")) {
                        errorLog(" _internal_create_secure_channel err = ", err.message);
                        // the server may have shut down the channel because its certificate
                        // has changed ....
                        // let request the server certificate again ....
                        debugLog(chalk.bgWhite.red("ClientBaseImpl: Server Certificate has changed." +
                            " we need to retrieve server certificate again"));

                        return this.fetchServerCertificate(this.endpointUrl, (err1?: Error | null) => {
                            if (err1) {
                                return failAndRetry(err1, "trying to fetch new server certificate");
                            }
                            this._internal_create_secure_channel(infiniteConnectionRetry, (err3?: Error | null) => {
                                if (err3) {
                                    return failAndRetry(err3, "trying to create new channel with new certificate");
                                }
                                callback();
                            });
                        });

                    }
                    debugLog(chalk.bgWhite.red("ClientBaseImpl: cannot reconnect .."));
                    failAndRetry(err!, "cannot create secure channel");

                } else {

                    /**
                     * notify the observers that the reconnection process has been completed
                     * @event after_reconnection
                     * @param err
                     */
                    this.emit("after_reconnection", err); // send after callback
                    assert(this._secureChannel, "expecting a secureChannel here ");
                    // a new channel has be created and a new connection is established
                    debugLog(chalk.bgWhite.red("ClientBaseImpl:  RECONNECTED                !!!"));
                    return callback();
                }
            }


(err1?: Error | null) => {
                            if (err1) {
                                return failAndRetry(err1, "trying to fetch new server certificate");
                            }
                            this._internal_create_secure_channel(infiniteConnectionRetry, (err3?: Error | null) => {
                                if (err3) {
                                    return failAndRetry(err3, "trying to create new channel with new certificate");
                                }
                                callback();
                            });
                        }


(err3?: Error | null) => {
                                if (err3) {
                                    return failAndRetry(err3, "trying to create new channel with new certificate");
                                }
                                callback();
                            }


() => {
            _attempt_to_recreate_secure_channel();
        }


// ------------------------------------------------- STEP 2 : OpenSecureChannel
(_innerCallback: ErrorCallback) => {

                secureChannel = new ClientSecureChannelLayer({
                    connectionStrategy,
                    defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
                    parent: this,
                    securityMode: this.securityMode,
                    securityPolicy: this.securityPolicy,
                    serverCertificate: this.serverCertificate,
                    tokenRenewalInterval: this.tokenRenewalInterval
                });

                this._secureChannel = secureChannel;

                secureChannel.protocolVersion = this.protocolVersion;

                secureChannel.create(this.endpointUrl, (err?: Error) => {
                    if (err) {
                        debugLog(chalk.yellow("Cannot create secureChannel"),
                            (err.message ? chalk.cyan(err.message) : ""));
                        this._destroy_secure_channel();
                    } else {
                        if (!this._secureChannel) {
                            debugLog("_secureChannel has been closed during the transaction !");
                            this._destroy_secure_channel();
                            return _innerCallback(new Error("Secure Channel Closed"));
                        }
                        assert(this._secureChannel !== null);
                        this._install_secure_channel_event_handlers(secureChannel);
                    }
                    assert(err || this._secureChannel !== null);
                    _innerCallback(err);
                });

                secureChannel.on("backoff", (count: number, delay: number) => {
                    this.emit("backoff", count, delay);
                });

                secureChannel.on("abort", () => {
                    this.emit("abort");
                });

            }


(err?: Error) => {
                    if (err) {
                        debugLog(chalk.yellow("Cannot create secureChannel"),
                            (err.message ? chalk.cyan(err.message) : ""));
                        this._destroy_secure_channel();
                    } else {
                        if (!this._secureChannel) {
                            debugLog("_secureChannel has been closed during the transaction !");
                            this._destroy_secure_channel();
                            return _innerCallback(new Error("Secure Channel Closed"));
                        }
                        assert(this._secureChannel !== null);
                        this._install_secure_channel_event_handlers(secureChannel);
                    }
                    assert(err || this._secureChannel !== null);
                    _innerCallback(err);
                }


(count: number, delay: number) => {
                    this.emit("backoff", count, delay);
                }


() => {
                    this.emit("abort");
                }


// ------------------------------------------------- STEP 3 : GetEndpointsRequest
(innerCallback: ErrorCallback) => {

                if (!this.knowsServerEndpoint) {
                    assert(this._secureChannel !== null);
                    this.getEndpoints((err: Error | null/*, endpoints?: EndpointDescription[]*/) => {
                        // Xx endpoints;
                        assert(this._secureChannel !== null);
                        innerCallback(err ? err : undefined);
                    });
                } else {
                    // end points are already known
                    assert(this._secureChannel !== null);
                    innerCallback();
                }
            }


(err: Error | null/*, endpoints?: EndpointDescription[]*/) => {
                        // Xx endpoints;
                        assert(this._secureChannel !== null);
                        innerCallback(err ? err : undefined);
                    }


(err) => {

            if (err) {
                this._secureChannel = null;
                callback(err);
            } else {
                assert(this._secureChannel !== null);
                callback(null, secureChannel);
            }
        }


() => {
                callback(new Error("connect already called"));
            }


(err?: Error) => {
                if (err) {
                    return callback(err);
                }
                this.connect(endpointUrl, callback);
            }


(err?: Error) => setImmediate(() => callback(err))


() => callback(err)


(err: Error | null /* secureChannel?: ClientSecureChannelLayer*/) => {
            // xx secureChannel;
            if (!err) {
                this.emit("connected");
                callbackOnceDelayed(err!);
            } else {

                OPCUAClientBase.registry.unregister(this);
                uninstallPeriodicClockAdjustmement();

                debugLog(chalk.red("SecureChannel creation has failed with error :", err.message));
                if (err.message.match(/ECONNREF/)) {
                    debugLog(chalk.yellow("- The client cannot to :" + endpointUrl + ". Server is not reachable."));
                    err = new Error("The connection cannot be established with server " + endpointUrl + " .\n" +
                        "Please check that the server is up and running or your network configuration.\n" +
                        "Err = (" + err.message + ")");

                } else {
                    debugLog(chalk.yellow("  - The client certificate may not be trusted by the server"));
                    err = new Error("The connection has been rejected by server,\n" +
                        "Please check that client certificate is trusted by server.\n" +
                        "Err = (" + err.message + ")");
                }
                this.emit("connection_failed", err);
                callbackOnceDelayed(err!);
            }
        }


(endpoint) => {
            return endpoint.securityMode === securityMode &&
                endpoint.securityPolicyUri === securityPolicy;
        }


(endpoint: EndpointDescription) => {
            return endpoint.endpointUrl === endpointUrl &&
                endpoint.securityMode === securityMode &&
                endpoint.securityPolicyUri === securityPolicy;
        }


(err: Error | null, response?: Response) => {
            this._serverEndpoints = [];
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof GetEndpointsResponse)) {
                return callback(new Error("Internal Error"));
            }
            if (response && response.endpoints) {
                this._serverEndpoints = response.endpoints;
            }
            callback(null, this._serverEndpoints);
        }


() => {
                callback(new Error("Invalid Secure Channel"));
            }


(err: Error | null, response?: Response) => {
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof FindServersResponse)) {
                return callback(new Error("Internal Error"));
            }
            response.servers = response.servers || [];

            callback(null, response.servers);
        }


(err: Error | null, response?: Response) => {
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof FindServersOnNetworkResponse)) {
                return new Error("Internal Error");
            }
            response.servers = response.servers || [];
            callback(null, response.servers);
        }


(err?: Error) => {

                debugLog("ClientBaseImpl#disconnect reconnection has been canceled");

                assert(!err, " why would this fail ?");
                assert(!this.isReconnecting);
                // sessions cannot be cancelled properly and must be discarded.
                this.disconnect(callback);
            }


(/*err*/) => {
                this.disconnect(callback);
            }


(session: ClientSessionImpl) => {
                this._removeSession(session);
            }


() => {
                this._secureChannel = tmpChannel;
                tmpChannel = null;
                this._destroy_secure_channel();
                setImmediate(callback);
            }


(err: Error | null, result?: FindEndpointResult) => {
            if (err) {
                this.emit("connection_failed", err);
                return callback(err);
            }

            if (!result) {
                const err1 = new Error("internal error");
                this.emit("connection_failed", err1);
                return callback(err1);
            }

            const endpoint = result.selectedEndpoint;
            if (!endpoint) {
                // no matching end point can be found ...
                const err1 = new Error("cannot find endpoint");
                this.emit("connection_failed", err1);
                return callback(err1);
            }

            assert(endpoint);

            _verify_serverCertificate(endpoint.serverCertificate, (err1?: Error) => {
                if (err1) {
                    this.emit("connection_failed", err1);
                    return callback(err1);
                }
                this.serverCertificate = endpoint.serverCertificate;
                callback();
            });
        }


(err1?: Error) => {
                if (err1) {
                    this.emit("connection_failed", err1);
                    return callback(err1);
                }
                this.serverCertificate = endpoint.serverCertificate;
                callback();
            }


(session: ClientSessionImpl, next: () => void) => {
            assert(session._client === this);

            // note: to prevent next to be call twice
            let _next_already_call = false;
            session.close((err?: Error) => {

                if (_next_already_call) {
                    return;
                }
                _next_already_call = true;

                // We should not bother if we have an error here
                // Session may fail to close , if they haven't been activate and forcefully closed by server
                // in a attempt to preserve resources in the case of a DDOS attack for instance.
                if (err) {
                    const msg = session.authenticationToken ? session.authenticationToken.toString() : "";
                    debugLog(" failing to close session " + msg);
                }
                next();
            });

        }


(err?: Error) => {

                if (_next_already_call) {
                    return;
                }
                _next_already_call = true;

                // We should not bother if we have an error here
                // Session may fail to close , if they haven't been activate and forcefully closed by server
                // in a attempt to preserve resources in the case of a DDOS attack for instance.
                if (err) {
                    const msg = session.authenticationToken ? session.authenticationToken.toString() : "";
                    debugLog(" failing to close session " + msg);
                }
                next();
            }


(err) => {

            // istanbul ignore next
            if (this._sessions.length > 0) {
                debugLog(this._sessions.map((s: ClientSessionImpl) =>
                    s.authenticationToken ? s.authenticationToken.toString() : "").join(" "));
            }
            assert(this._sessions.length === 0, " failed to disconnect exiting sessions ");
            callback(err!);
        }


(s: ClientSessionImpl) =>
                    s.authenticationToken ? s.authenticationToken.toString() : ""


(chunk: Buffer) => {
            /**
             * notify the observer that a message_chunk has been sent
             * @event send_chunk
             * @param message_chunk
             */
            this.emit("send_chunk", chunk);
        }


(chunk: Buffer) => {
            /**
             * notify the observer that a message_chunk has been received
             * @event receive_chunk
             * @param message_chunk
             */
            this.emit("receive_chunk", chunk);
        }


(message: Request) => {
            /**
             * notify the observer that a request has been sent to the server.
             * @event send_request
             * @param message
             */
            this.emit("send_request", message);
        }


(message: Response) => {
            /**
             * notify the observer that a response has been received from the server.
             * @event receive_response
             * @param message
             */
            this.emit("receive_response", message);
        }


(token: SecurityToken) => {
            // secureChannel requests a new token
            debugLog("SecureChannel Security Token ", token.tokenId, "live time was =", token.revisedLifetime,
                " is about to expired , it's time to request a new token");
            // forward message to upper level
            this.emit("lifetime_75", token, secureChannel);
        }


() => {
            // forward message to upper level
            this.emit("security_token_renewed", secureChannel);
        }


(err?: Error) => {

            debugLog(chalk.yellow.bold(" ClientBaseImpl emitting close"), err);

            if (!err || !this.reconnectOnFailure) {

                // this is a normal close operation initiated byu

                /**
                 * @event close
                 * @param error
                 */
                this.emit("close", err);

                setImmediate(() => {
                    this._destroy_secure_channel();
                });
                return;

            } else {

                this.emit("connection_lost");

                setImmediate(() => {

                    debugLog("recreating new secure channel ");

                    this._recreate_secure_channel((err1?: Error) => {

                        debugLog("secureChannel#on(close) => _recreate_secure_channel returns ",
                            err1 ? err1.message : "OK");

                        if (err1) {
                            // xx assert(!this._secureChannel);
                            debugLog("_recreate_secure_channel has failed");
                            // xx this.emit("close", err1);
                            return;
                        } else {
                            /**
                             * @event connection_reestablished
                             *        send when the connection is reestablished after a connection break
                             */
                            this.emit("connection_reestablished");

                            // now delegate to upper class the
                            if (this._on_connection_reestablished) {
                                assert(_.isFunction(this._on_connection_reestablished));
                                this._on_connection_reestablished((err2?: Error) => {

                                    if (err2) {
                                        debugLog("connection_reestablished has failed");
                                        this.disconnect(() => {
                                            //  callback(err);
                                            // tslint:disable-next-line: no-console
                                            console.log("Disconnected following reconnection failure", err2.message);
                                        });
                                    }
                                });
                            }
                        }
                    });
                });
            }
        }


() => {
                    this._destroy_secure_channel();
                }


() => {

                    debugLog("recreating new secure channel ");

                    this._recreate_secure_channel((err1?: Error) => {

                        debugLog("secureChannel#on(close) => _recreate_secure_channel returns ",
                            err1 ? err1.message : "OK");

                        if (err1) {
                            // xx assert(!this._secureChannel);
                            debugLog("_recreate_secure_channel has failed");
                            // xx this.emit("close", err1);
                            return;
                        } else {
                            /**
                             * @event connection_reestablished
                             *        send when the connection is reestablished after a connection break
                             */
                            this.emit("connection_reestablished");

                            // now delegate to upper class the
                            if (this._on_connection_reestablished) {
                                assert(_.isFunction(this._on_connection_reestablished));
                                this._on_connection_reestablished((err2?: Error) => {

                                    if (err2) {
                                        debugLog("connection_reestablished has failed");
                                        this.disconnect(() => {
                                            //  callback(err);
                                            // tslint:disable-next-line: no-console
                                            console.log("Disconnected following reconnection failure", err2.message);
                                        });
                                    }
                                });
                            }
                        }
                    });
                }


(err1?: Error) => {

                        debugLog("secureChannel#on(close) => _recreate_secure_channel returns ",
                            err1 ? err1.message : "OK");

                        if (err1) {
                            // xx assert(!this._secureChannel);
                            debugLog("_recreate_secure_channel has failed");
                            // xx this.emit("close", err1);
                            return;
                        } else {
                            /**
                             * @event connection_reestablished
                             *        send when the connection is reestablished after a connection break
                             */
                            this.emit("connection_reestablished");

                            // now delegate to upper class the
                            if (this._on_connection_reestablished) {
                                assert(_.isFunction(this._on_connection_reestablished));
                                this._on_connection_reestablished((err2?: Error) => {

                                    if (err2) {
                                        debugLog("connection_reestablished has failed");
                                        this.disconnect(() => {
                                            //  callback(err);
                                            // tslint:disable-next-line: no-console
                                            console.log("Disconnected following reconnection failure", err2.message);
                                        });
                                    }
                                });
                            }
                        }
                    }


(err2?: Error) => {

                                    if (err2) {
                                        debugLog("connection_reestablished has failed");
                                        this.disconnect(() => {
                                            //  callback(err);
                                            // tslint:disable-next-line: no-console
                                            console.log("Disconnected following reconnection failure", err2.message);
                                        });
                                    }
                                }


() => {
                                            //  callback(err);
                                            // tslint:disable-next-line: no-console
                                            console.log("Disconnected following reconnection failure", err2.message);
                                        }


(request: Request) => {
            /**
             * send when a request has timed out without receiving a response
             * @event timed_out_request
             * @param request
             */
            this.emit("timed_out_request", request);
        }


(options: OPCUAClientBaseOptions): OPCUAClientBase => {
    return new ClientBaseImpl(options);
}


public _cancel_reconnection(callback: ErrorCallback) {

        assert(this.isReconnecting);

        // istanbul ignore next
        this.reconnectionIsCanceled = true;
        if (!this._secureChannel) {
            return callback(); // nothing to do
        }
        this._secureChannel.abortConnection((/*err?: Error*/) => {
            this._secureChannel = null;
            callback();
        });
        (this as any).once("reconnection_canceled", () => {
            /* empty */
        });
    }


public _recreate_secure_channel(callback: ErrorCallback) {

        debugLog("_recreate_secure_channel...");

        if (!this.knowsServerEndpoint) {
            debugLog("Cannot reconnect , server endpoint is unknown");
            return callback(new Error("Cannot reconnect, server endpoint is unknown"));
        }
        assert(this.knowsServerEndpoint);
        assert(!this.isReconnecting);
        /**
         * notifies the observer that the OPCUA is now trying to reestablish the connection
         * after having received a connection break...
         * @event start_reconnection
         *
         */
        this.emit("start_reconnection"); // send after callback

        this._destroy_secure_channel();
        assert(!this._secureChannel);

        const infiniteConnectionRetry: ConnectionStrategyOptions = {
            initialDelay: this.connectionStrategy.initialDelay,
            maxDelay: this.connectionStrategy.maxDelay,
            maxRetry: -1
        };

        const failAndRetry = (err: Error, message: string) => {

            errorLog("client = ", this.clientName, message, err.message);

            if (this.reconnectionIsCanceled) {
                this.emit("reconnection_canceled");
                return callback(new Error("Reconnection has been canceled - " + this.clientName));
            }
            // else
            // let retry a little bit later
            this.emit("reconnection_attempt_has_failed", err, message); // send after callback
            setTimeout(_attempt_to_recreate_secure_channel, 100);
        };

        const _attempt_to_recreate_secure_channel = () => {

            if (this.reconnectionIsCanceled) {
                this.emit("reconnection_canceled");
                return callback(new Error("Reconnection has been canceled - " + this.clientName));
            }

            this._internal_create_secure_channel(infiniteConnectionRetry, (err?: Error | null) => {

                if (err) {
                    if (err.message.match("ECONNREFUSED")) {
                        return callback(err);
                    }
                    if (err.message.match("Backoff aborted.")) {
                        return failAndRetry(err!, "cannot create secure channel");
                    }
                    if (true || err!.message.match("BadCertificateInvalid")) {
                        errorLog(" _internal_create_secure_channel err = ", err.message);
                        // the server may have shut down the channel because its certificate
                        // has changed ....
                        // let request the server certificate again ....
                        debugLog(chalk.bgWhite.red("ClientBaseImpl: Server Certificate has changed." +
                            " we need to retrieve server certificate again"));

                        return this.fetchServerCertificate(this.endpointUrl, (err1?: Error | null) => {
                            if (err1) {
                                return failAndRetry(err1, "trying to fetch new server certificate");
                            }
                            this._internal_create_secure_channel(infiniteConnectionRetry, (err3?: Error | null) => {
                                if (err3) {
                                    return failAndRetry(err3, "trying to create new channel with new certificate");
                                }
                                callback();
                            });
                        });

                    }
                    debugLog(chalk.bgWhite.red("ClientBaseImpl: cannot reconnect .."));
                    failAndRetry(err!, "cannot create secure channel");

                } else {

                    /**
                     * notify the observers that the reconnection process has been completed
                     * @event after_reconnection
                     * @param err
                     */
                    this.emit("after_reconnection", err); // send after callback
                    assert(this._secureChannel, "expecting a secureChannel here ");
                    // a new channel has be created and a new connection is established
                    debugLog(chalk.bgWhite.red("ClientBaseImpl:  RECONNECTED                !!!"));
                    return callback();
                }
            });
        };

        // create a secure channel
        // a new secure channel must be established
        setImmediate(() => {
            _attempt_to_recreate_secure_channel();
        });

    }


public _internal_create_secure_channel(
        connectionStrategy: ConnectionStrategyOptions,
        callback: CreateSecureChannelCallbackFunc
    ) {

        let secureChannel: ClientSecureChannelLayer;

        assert(this._secureChannel === null);
        assert(_.isString(this.endpointUrl));

        async.series([

            // ------------------------------------------------- STEP 2 : OpenSecureChannel
            (_innerCallback: ErrorCallback) => {

                secureChannel = new ClientSecureChannelLayer({
                    connectionStrategy,
                    defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
                    parent: this,
                    securityMode: this.securityMode,
                    securityPolicy: this.securityPolicy,
                    serverCertificate: this.serverCertificate,
                    tokenRenewalInterval: this.tokenRenewalInterval
                });

                this._secureChannel = secureChannel;

                secureChannel.protocolVersion = this.protocolVersion;

                secureChannel.create(this.endpointUrl, (err?: Error) => {
                    if (err) {
                        debugLog(chalk.yellow("Cannot create secureChannel"),
                            (err.message ? chalk.cyan(err.message) : ""));
                        this._destroy_secure_channel();
                    } else {
                        if (!this._secureChannel) {
                            debugLog("_secureChannel has been closed during the transaction !");
                            this._destroy_secure_channel();
                            return _innerCallback(new Error("Secure Channel Closed"));
                        }
                        assert(this._secureChannel !== null);
                        this._install_secure_channel_event_handlers(secureChannel);
                    }
                    assert(err || this._secureChannel !== null);
                    _innerCallback(err);
                });

                secureChannel.on("backoff", (count: number, delay: number) => {
                    this.emit("backoff", count, delay);
                });

                secureChannel.on("abort", () => {
                    this.emit("abort");
                });

            },
            // ------------------------------------------------- STEP 3 : GetEndpointsRequest
            (innerCallback: ErrorCallback) => {

                if (!this.knowsServerEndpoint) {
                    assert(this._secureChannel !== null);
                    this.getEndpoints((err: Error | null/*, endpoints?: EndpointDescription[]*/) => {
                        // Xx endpoints;
                        assert(this._secureChannel !== null);
                        innerCallback(err ? err : undefined);
                    });
                } else {
                    // end points are already known
                    assert(this._secureChannel !== null);
                    innerCallback();
                }
            }

        ], (err) => {

            if (err) {
                this._secureChannel = null;
                callback(err);
            } else {
                assert(this._secureChannel !== null);
                callback(null, secureChannel);
            }
        });

    }


/**
     * connect the OPC-UA client to a server end point.
     * @async
     */
public connect(endpointUrl: string): Promise<void>;


public connect(endpointUrl: string, callback: ErrorCallback): void;


public connect(...args: any[]): any {

        const endpointUrl = args[0];
        const callback = args[1];
        assert(_.isFunction(callback), "expecting a callback");

        this.endpointUrl = endpointUrl;

        debugLog("ClientBaseImpl#connect ", endpointUrl);

        // prevent illegal call to connect
        if (this._secureChannel !== null) {
            setImmediate(() => {
                callback(new Error("connect already called"));
            });
            return;
        }

        if (!this.serverCertificate && this.securityMode !== MessageSecurityMode.None) {

            return this.fetchServerCertificate(endpointUrl, (err?: Error) => {
                if (err) {
                    return callback(err);
                }
                this.connect(endpointUrl, callback);
            });
        }

        // todo: make sure endpointUrl exists in the list of endpoints send by the server
        // [...]

        // make sure callback will only be call once regardless of outcome, and will be also deferred.
        const callbackOnceDelayed: any = once((err?: Error) => setImmediate(() => callback(err)));

        installPeriodicClockAdjustmement();
        OPCUAClientBase.registry.register(this);

        this._internal_create_secure_channel(this.connectionStrategy, (err: Error | null /* secureChannel?: ClientSecureChannelLayer*/) => {
            // xx secureChannel;
            if (!err) {
                this.emit("connected");
                callbackOnceDelayed(err!);
            } else {

                OPCUAClientBase.registry.unregister(this);
                uninstallPeriodicClockAdjustmement();

                debugLog(chalk.red("SecureChannel creation has failed with error :", err.message));
                if (err.message.match(/ECONNREF/)) {
                    debugLog(chalk.yellow("- The client cannot to :" + endpointUrl + ". Server is not reachable."));
                    err = new Error("The connection cannot be established with server " + endpointUrl + " .\n" +
                        "Please check that the server is up and running or your network configuration.\n" +
                        "Err = (" + err.message + ")");

                } else {
                    debugLog(chalk.yellow("  - The client certificate may not be trusted by the server"));
                    err = new Error("The connection has been rejected by server,\n" +
                        "Please check that client certificate is trusted by server.\n" +
                        "Err = (" + err.message + ")");
                }
                this.emit("connection_failed", err);
                callbackOnceDelayed(err!);
            }
        });

    }


public getClientNonce(): Nonce | null {
        return this._secureChannel ? this._secureChannel.getClientNonce() : null;
    }


public performMessageTransaction(request: Request, callback: ResponseCallback<Response>) {

        if (!this._secureChannel) {
            // this may happen if the Server has closed the connection abruptly for some unknown reason
            // or if the tcp connection has been broken.
            return callback(new Error("No SecureChannel , connection may have been canceled abruptly by server"));
        }
        assert(this._secureChannel);
        assert(request);
        assert(request.requestHeader);
        assert(typeof callback === "function");
        this._secureChannel.performMessageTransaction(request, callback as any);
    }


/**
     *
     * return the endpoint information matching  security mode and security policy.
     * @method findEndpoint
     */
public findEndpointForSecurity(
        securityMode: MessageSecurityMode,
        securityPolicy: SecurityPolicy
    ): EndpointDescription | undefined {
        securityMode = coerceMessageSecurityMode(securityMode);
        securityPolicy = coerceSecurityPolicy(securityPolicy);
        assert(this.knowsServerEndpoint, "Server end point are not known yet");
        return _.find(this._serverEndpoints, (endpoint) => {
            return endpoint.securityMode === securityMode &&
                endpoint.securityPolicyUri === securityPolicy;
        });
    }


/**
     *
     * return the endpoint information matching the specified url , security mode and security policy.
     * @method findEndpoint
     */
public findEndpoint(
        endpointUrl: string,
        securityMode: MessageSecurityMode,
        securityPolicy: SecurityPolicy
    ): EndpointDescription | undefined {

        assert(this.knowsServerEndpoint, "Server end point are not known yet");
        if (!this._serverEndpoints || this._serverEndpoints.length === 0) {
            return undefined;
        }
        return _.find(this._serverEndpoints, (endpoint: EndpointDescription) => {
            return endpoint.endpointUrl === endpointUrl &&
                endpoint.securityMode === securityMode &&
                endpoint.securityPolicyUri === securityPolicy;
        });
    }


public async getEndpoints(options?: GetEndpointsOptions): Promise<EndpointDescription[]>;


public getEndpoints(options: GetEndpointsOptions, callback: ResponseCallback<EndpointDescription[]>): void;


public getEndpoints(callback: ResponseCallback<EndpointDescription[]>): void;


public getEndpoints(...args: any[]): any {
        if (args.length === 1) {
            return this.getEndpoints({}, args[0]);
        }
        const options = args[0] as GetEndpointsOptions;
        const callback = args[1] as ResponseCallback<EndpointDescription[]>;
        assert(_.isFunction(callback));

        options.localeIds = options.localeIds || [];
        options.profileUris = options.profileUris || [];

        const request = new GetEndpointsRequest({
            endpointUrl: options.endpointUrl || this.endpointUrl,
            localeIds: options.localeIds,
            profileUris: options.profileUris,
            requestHeader: {
                auditEntryId: null
            }
        });

        this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
            this._serverEndpoints = [];
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof GetEndpointsResponse)) {
                return callback(new Error("Internal Error"));
            }
            if (response && response.endpoints) {
                this._serverEndpoints = response.endpoints;
            }
            callback(null, this._serverEndpoints);
        });
    }


public getEndpointsRequest(options: any, callback: any) {
        warningLog("note: ClientBaseImpl#getEndpointsRequest is deprecated, use ClientBaseImpl#getEndpoints instead");
        return this.getEndpoints(options, callback);
    }


/**
     * @method findServers
     */
public findServers(options?: FindServersRequestLike): Promise<ApplicationDescription[]>;


public findServers(options: FindServersRequestLike, callback: ResponseCallback<ApplicationDescription[]>): void;


public findServers(callback: ResponseCallback<ApplicationDescription[]>): void;


public findServers(...args: any[]): any {

        if (!this._secureChannel) {
            setImmediate(() => {
                callback(new Error("Invalid Secure Channel"));
            });
            return;
        }

        if (args.length === 1) {
            return this.findServers({}, args[0]);
        }
        const options = args[0] as FindServersRequestLike;
        const callback = args[1] as ResponseCallback<ApplicationDescription[]>;

        const request = new FindServersRequest({
            endpointUrl: options.endpointUrl || this.endpointUrl,
            localeIds: options.localeIds || [],
            serverUris: options.serverUris || []
        });

        this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof FindServersResponse)) {
                return callback(new Error("Internal Error"));
            }
            response.servers = response.servers || [];

            callback(null, response.servers);
        });
    }


public findServersOnNetwork(options?: FindServersOnNetworkRequestLike): Promise<ServerOnNetwork[]>;


public findServersOnNetwork(callback: ResponseCallback<ServerOnNetwork[]>): void;


public findServersOnNetwork(
        options: FindServersOnNetworkRequestLike, callback: ResponseCallback<ServerOnNetwork[]>): void;


public findServersOnNetwork(...args: any[]): any {

        if (args.length === 1) {
            return this.findServersOnNetwork({}, args[0]);
        }
        const options = args[0] as FindServersOnNetworkRequestOptions;
        const callback = args[1] as ResponseCallback<ServerOnNetwork[]>;

        if (!this._secureChannel) {
            setImmediate(() => {
                callback(new Error("Invalid Secure Channel"));
            });
            return;
        }

        const request = new FindServersOnNetworkRequest(options);

        this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
            if (err) {
                return callback(err);
            }
            if (!response || !(response instanceof FindServersOnNetworkResponse)) {
                return new Error("Internal Error");
            }
            response.servers = response.servers || [];
            callback(null, response.servers);
        });
    }


public _removeSession(session: ClientSessionImpl) {

        const index = this._sessions.indexOf(session);

        if (index >= 0) {
            const s = this._sessions.splice(index, 1)[0];
            assert(s === session);
            assert(!_.contains(this._sessions, session));
            assert(session._client === this);
            session._client = null;
        }
        assert(!_.contains(this._sessions, session));
    }


public disconnect(): Promise<void>;


public disconnect(callback: ErrorCallback): void;


public disconnect(...args: any[]): any {

        const callback = args[0];
        assert(_.isFunction(callback), "expecting a callback function here");
        debugLog("ClientBaseImpl#disconnect", this.endpointUrl);
        if (this.isReconnecting) {
            debugLog("ClientBaseImpl#disconnect called while reconnection is in progress");
            // let's abort the reconnection process
            return this._cancel_reconnection((err?: Error) => {

                debugLog("ClientBaseImpl#disconnect reconnection has been canceled");

                assert(!err, " why would this fail ?");
                assert(!this.isReconnecting);
                // sessions cannot be cancelled properly and must be discarded.
                this.disconnect(callback);
            });
        }

        if (this._sessions.length && !this.keepPendingSessionsOnDisconnect) {
            debugLog("warning : disconnection : closing pending sessions");
            // disconnect has been called whereas living session exists
            // we need to close them first ....
            this._close_pending_sessions((/*err*/) => {
                this.disconnect(callback);
            });
            return;
        }

        if (this._sessions.length) {
            // transfer active session to  orphan and detach them from channel
            _.forEach(this._sessions, (session: ClientSessionImpl) => {
                this._removeSession(session);
            });
            this._sessions = [];
        }
        assert(this._sessions.length === 0, " attempt to disconnect a client with live sessions ");

        OPCUAClientBase.registry.unregister(this);
        uninstallPeriodicClockAdjustmement();

        if (this._secureChannel) {

            let tmpChannel: any = this._secureChannel;
            this._secureChannel = null;

            tmpChannel.close(() => {
                this._secureChannel = tmpChannel;
                tmpChannel = null;
                this._destroy_secure_channel();
                setImmediate(callback);
            });
        } else {
            this.emit("close", null);
            setImmediate(callback);
        }
    }


// override me !
public _on_connection_reestablished(callback: ErrorCallback) {
        callback();
    }


public toString(): string {

        let str = "";
        str += "  defaultSecureTokenLifetime.... " + this.defaultSecureTokenLifetime + "\n";
        str += "  securityMode.................. " + this.securityMode.toString() + "\n";
        str += "  securityPolicy................ " + this.securityPolicy.toString() + "\n";
        str += "  certificate fingerprint....... " + makeSHA1Thumbprint(this.getCertificate()).toString("hex") + "\n";
        // this.serverCertificate = options.serverCertificate || null + "\n";
        str += "  keepSessionAlive.............. " + this.keepSessionAlive + "\n";
        str += "  bytesRead..................... " + this.bytesRead + "\n";
        str += "  bytesWritten.................. " + this.bytesWritten + "\n";
        str += "  transactionsPerformed......... " + this.transactionsPerformed + "\n";
        str += "  timedOutRequestCount.......... " + this.timedOutRequestCount + "\n";
        str += "  connectionStrategy." + "\n";
        str += "        .maxRetry............... " + this.connectionStrategy.maxRetry + "\n";
        str += "        .initialDelay........... " + this.connectionStrategy.initialDelay + "\n";
        str += "        .maxDelay............... " + this.connectionStrategy.maxDelay + "\n";
        str += "        .randomisationFactor.... " + this.connectionStrategy.randomisationFactor + "\n";
        str += "  keepSessionAlive.............. " + this.keepSessionAlive + "\n";
        if (this._secureChannel) {
            str += "secureChannel:\n" + this._secureChannel.toString();
        }
        return str;
    }


protected _addSession(session: ClientSessionImpl) {
        assert(!session._client || session._client === this);
        assert(!_.contains(this._sessions, session), "session already added");
        session._client = this;
        this._sessions.push(session);

        if (this.keepSessionAlive) {
            session.startKeepAliveManager();
        }
    }