Example #1
0
export function initNativeHandlers(contentRef: ContentRef, store: *) {
  const state$ = from(store).pipe(share());

  return createTitleFeed(contentRef, state$).subscribe(
    setTitleFromAttributes,
    err => console.error(err)
  );
}
 /**
  * @param {?=} config
  */
 constructor(config = {}) {
     this.isSupported = false;
     this.notifyOptions = {
         setItem: false,
         removeItem: false
     };
     this.prefix = 'ls';
     this.storageType = 'localStorage';
     this.errors = new Subscriber();
     this.removeItems = new Subscriber();
     this.setItems = new Subscriber();
     this.warnings = new Subscriber();
     let { notifyOptions, prefix, storageType } = config;
     if (notifyOptions != null) {
         let { setItem, removeItem } = notifyOptions;
         this.setNotify(!!setItem, !!removeItem);
     }
     if (prefix != null) {
         this.setPrefix(prefix);
     }
     if (storageType != null) {
         this.setStorageType(storageType);
     }
     this.errors$ = new Observable((/**
      * @param {?} observer
      * @return {?}
      */
     (observer) => this.errors = observer)).pipe(share());
     this.removeItems$ = new Observable((/**
      * @param {?} observer
      * @return {?}
      */
     (observer) => this.removeItems = observer)).pipe(share());
     this.setItems$ = new Observable((/**
      * @param {?} observer
      * @return {?}
      */
     (observer) => this.setItems = observer)).pipe(share());
     this.warnings$ = new Observable((/**
      * @param {?} observer
      * @return {?}
      */
     (observer) => this.warnings = observer)).pipe(share());
     this.isSupported = this.checkSupport();
 }
Example #3
0
    link: function ($scope, $elem, attrs, ngModel) {
      if ($elem.prop('multiple')) {
        throw new Error(multipleUsageErrorMessage);
      }

      const maxSizeValidator = (dataUrl) => {
        return {
          errorKey: 'maxSize',
          isValid: attrs.max === '' || dataUrl.length <= parseInt(attrs.max)
        };
      };

      const validators = [ maxSizeValidator ];

      // produce fileContent$ whenever the $element 'change' event is triggered.
      const fileContent$ = Rx.fromEvent($elem, 'change', e => e).pipe(
        map(e => e.target.files),
        switchMap(files => {
          if (files.length === 0) {
            return [];
          }

          if (files.length > 1) {
            throw new Error(multipleUsageErrorMessage);
          }

          return createFileContent$(files[0]);
        }),
        share()
      );

      // validate the content of the files after it is loaded
      const validations$ = fileContent$.pipe(
        map(fileContent => (
          validators.map(validator => validator(fileContent))
        ))
      );

      // push results from input/validation to the ngModel
      const unsubscribe = Rx
        .combineLatest(fileContent$, validations$)
        .subscribe(([ fileContent, validations ]) => {
          $scope.$evalAsync(() => {
            validations.forEach(validation => {
              ngModel.$setValidity(validation.errorKey, validation.isValid);
            });

            if (validations.every(validation => validation.isValid)) {
              ngModel.$setViewValue(fileContent);
            }
          });
        }, (err) => {
          throw err;
        });

      $scope.$on('destroy', unsubscribe);
    }
 TranslateService.prototype.getTranslation = function (lang) {
     var _this = this;
     this.pending = true;
     this.loadingTranslations = this.currentLoader.getTranslation(lang).pipe(share());
     this.loadingTranslations.pipe(take(1))
         .subscribe(function (res) {
         _this.translations[lang] = _this.compiler.compileTranslations(res, lang);
         _this.updateLangs();
         _this.pending = false;
     }, function (err) {
         _this.pending = false;
     });
     return this.loadingTranslations;
 };
Example #5
0
 constructor(props) {
   super(props);
   const { vis } = props;
   this.appState = vis.API.getAppState();
   this.state = {
     model: props.visParams,
     dirty: false,
     autoApply: true,
     visFields: props.visFields
   };
   this.onBrush = brushHandler(props.vis.API.timeFilter);
   this.visDataSubject = new Rx.Subject();
   this.visData$ = this.visDataSubject.asObservable().pipe(share());
 }
Example #6
0
 bindMessageHandlers(client, handlers, transform) {
     const disconnect$ = rxjs_1.fromEvent(client, 'disconnect').pipe(operators_1.share(), operators_1.first());
     handlers.forEach(({ message, callback }) => {
         const source$ = rxjs_1.fromEvent(client, message).pipe(operators_1.mergeMap((payload) => {
             const { data, ack } = this.mapPayload(payload);
             return transform(callback(data)).pipe(operators_1.filter(response => !shared_utils_1.isNil(response)), operators_1.map(response => [response, ack]));
         }), operators_1.takeUntil(disconnect$));
         source$.subscribe(([response, ack]) => {
             if (response.event) {
                 return client.emit(response.event, response.data);
             }
             shared_utils_1.isFunction(ack) && ack(response);
         });
     });
 }
 test("throws an error if given a bad action", done => {
   const actionBuffer = [];
   const action$ = ActionsObservable.of({
     type: actionTypes.LAUNCH_KERNEL
   }).pipe(share());
   const obs = launchKernelEpic(action$);
   obs.subscribe(
     x => {
       expect(x.type).toEqual(actionTypes.LAUNCH_KERNEL_FAILED);
       actionBuffer.push(x.type);
       done();
     },
     err => done.fail(err)
   );
 });
Example #8
0
export function safeChildProcess(childProcess, observer) {
  const ownTerminateSignal$ = Rx.merge(
    Rx.fromEvent(process, 'SIGTERM').pipe(mapTo('SIGTERM')),
    Rx.fromEvent(process, 'SIGINT').pipe(mapTo('SIGINT')),
    Rx.fromEvent(process, 'SIGBREAK').pipe(mapTo('SIGBREAK')),
  )
    .pipe(
      take(1),
      share()
    );

  // signals that will be sent to the child process as a result of the main process
  // being sent these signals, or the exit being triggered
  const signalForChildProcess$ = Rx.merge(
    // SIGKILL when this process gets a terminal signal
    ownTerminateSignal$.pipe(
      mapTo('SIGKILL')
    ),

    // SIGKILL when this process forcefully exits
    Rx.fromEvent(process, 'exit').pipe(
      take(1),
      mapTo('SIGKILL')
    ),
  );

    // send termination signals
  const terminate$ = Rx.merge(
    signalForChildProcess$.pipe(
      tap(signal => childProcess.kill(signal))
    ),

    ownTerminateSignal$.pipe(
      delay(1),
      tap(signal => process.kill(process.pid, signal)),
    )
  );

  // this is adding unsubscribe logic to our observer
  // so that if our observer unsubscribes, we terminate our child-process
  observer.add(() => {
    childProcess.kill('SIGKILL');
  });

  observer.add(terminate$.pipe(ignoreElements()).subscribe(observer));
}
Example #9
0
 test("Errors on an action where source not a string", done => {
   const badAction$ = ActionsObservable.of(
     actions.executeCell({ id: "id" })
   ).pipe(share());
   const responseActions = executeCellEpic(badAction$, store).pipe(
     catchError(error => {
       expect(error.message).toEqual("execute cell needs source string");
     })
   );
   responseActions.subscribe(
     // Every action that goes through should get stuck on an array
     x => {
       expect(x.type).toEqual(actionTypes.EXECUTE_FAILED);
       done();
     },
     err => done.fail(err)
   );
 });
Example #10
0
 /**
  * @return {?}
  */
 createActionStreams() {
     // Listens to all changes based on our instanceId
     const /** @type {?} */ changes$ = this.createChangesObservable().pipe(share());
     // Listen for the start action
     const /** @type {?} */ start$ = changes$.pipe(filter((change) => change.type === ExtensionActionTypes.START));
     // Listen for the stop action
     const /** @type {?} */ stop$ = changes$.pipe(filter((change) => change.type === ExtensionActionTypes.STOP));
     // Listen for lifted actions
     const /** @type {?} */ liftedActions$ = changes$.pipe(filter(change => change.type === ExtensionActionTypes.DISPATCH), map(change => this.unwrapAction(change.payload)));
     // Listen for unlifted actions
     const /** @type {?} */ actions$ = changes$.pipe(filter(change => change.type === ExtensionActionTypes.ACTION), map(change => this.unwrapAction(change.payload)));
     const /** @type {?} */ actionsUntilStop$ = actions$.pipe(takeUntil(stop$));
     const /** @type {?} */ liftedUntilStop$ = liftedActions$.pipe(takeUntil(stop$));
     this.start$ = start$.pipe(takeUntil(stop$));
     // Only take the action sources between the start/stop events
     this.actions$ = this.start$.pipe(switchMap(() => actionsUntilStop$));
     this.liftedActions$ = this.start$.pipe(switchMap(() => liftedUntilStop$));
 }
Example #11
0
 DevtoolsExtension.prototype.createActionStreams = function () {
     var _this = this;
     // Listens to all changes based on our instanceId
     var changes$ = this.createChangesObservable().pipe(share());
     // Listen for the start action
     var start$ = changes$.pipe(filter(function (change) { return change.type === ExtensionActionTypes.START; }));
     // Listen for the stop action
     var stop$ = changes$.pipe(filter(function (change) { return change.type === ExtensionActionTypes.STOP; }));
     // Listen for lifted actions
     var liftedActions$ = changes$.pipe(filter(function (change) { return change.type === ExtensionActionTypes.DISPATCH; }), map(function (change) { return _this.unwrapAction(change.payload); }));
     // Listen for unlifted actions
     var actions$ = changes$.pipe(filter(function (change) { return change.type === ExtensionActionTypes.ACTION; }), map(function (change) { return _this.unwrapAction(change.payload); }));
     var actionsUntilStop$ = actions$.pipe(takeUntil(stop$));
     var liftedUntilStop$ = liftedActions$.pipe(takeUntil(stop$));
     this.start$ = start$.pipe(takeUntil(stop$));
     // Only take the action sources between the start/stop events
     this.actions$ = this.start$.pipe(switchMap(function () { return actionsUntilStop$; }));
     this.liftedActions$ = this.start$.pipe(switchMap(function () { return liftedUntilStop$; }));
 };
Example #12
0
module.exports = function(rl) {
  var keypress = fromEvent(rl.input, 'keypress', normalizeKeypressEvents)
    // Ignore `enter` key. On the readline, we only care about the `line` event.
    .pipe(filter(({ key }) => key.name !== 'enter' && key.name !== 'return'));

  return {
    line: fromEvent(rl, 'line'),
    keypress: keypress,

    normalizedUpKey: keypress.pipe(
      filter(
        ({ key }) =>
          key.name === 'up' || key.name === 'k' || (key.name === 'p' && key.ctrl)
      ),
      share()
    ),

    normalizedDownKey: keypress.pipe(
      filter(
        ({ key }) =>
          key.name === 'down' || key.name === 'j' || (key.name === 'n' && key.ctrl)
      ),
      share()
    ),

    numberKey: keypress.pipe(
      filter(e => e.value && '123456789'.indexOf(e.value) >= 0),
      map(e => Number(e.value)),
      share()
    ),

    spaceKey: keypress.pipe(
      filter(({ key }) => key && key.name === 'space'),
      share()
    ),
    aKey: keypress.pipe(
      filter(({ key }) => key && key.name === 'a'),
      share()
    ),
    iKey: keypress.pipe(
      filter(({ key }) => key && key.name === 'i'),
      share()
    )
  };
};
Example #13
0
export function fromRef(ref, event, listenType) {
    if (listenType === void 0) { listenType = 'on'; }
    return new Observable(function (subscriber) {
        var fn = ref[listenType](event, function (snapshot, prevKey) {
            subscriber.next({ snapshot: snapshot, prevKey: prevKey });
            if (listenType == 'once') {
                subscriber.complete();
            }
        }, subscriber.error.bind(subscriber));
        if (listenType == 'on') {
            return { unsubscribe: function () { ref.off(event, fn); } };
        }
        else {
            return { unsubscribe: function () { } };
        }
    }).pipe(map(function (payload) {
        var snapshot = payload.snapshot, prevKey = payload.prevKey;
        var key = null;
        if (snapshot.exists()) {
            key = snapshot.key;
        }
        return { type: event, payload: snapshot, prevKey: prevKey, key: key };
    }), delay(0), share());
}
Example #14
0
export function findPluginSpecs(settings, configToMutate) {
  const config$ = Rx
    .defer(async () => {
      if (configToMutate) {
        return configToMutate;
      }

      return await defaultConfig(settings);
    })
    .pipe(shareReplay());

  // find plugin packs in configured paths/dirs
  const packageJson$ = config$.pipe(
    mergeMap(config => Rx.merge(
      ...config.get('plugins.paths').map(createPackageJsonAtPath$),
      ...config.get('plugins.scanDirs').map(createPackageJsonsInDirectory$)
    )),
    distinct(getDistinctKeyForFindResult),
    share()
  );

  const pack$ = createPack$(packageJson$).pipe(
    share()
  );

  const extendConfig$ = config$.pipe(
    mergeMap(config => (
      pack$.pipe(
        // get the specs for each found plugin pack
        mergeMap(({ pack }) => (
          pack ? pack.getPluginSpecs() : []
        )),
        // make sure that none of the plugin specs have conflicting ids, fail
        // early if conflicts detected or merge the specs back into the stream
        toArray(),
        mergeMap(allSpecs => {
          for (const [id, specs] of groupSpecsById(allSpecs)) {
            if (specs.length > 1) {
              throw new Error(
                `Multiple plugins found with the id "${id}":\n${
                  specs.map(spec => `  - ${id} at ${spec.getPath()}`).join('\n')
                }`
              );
            }
          }

          return allSpecs;
        }),
        mergeMap(async (spec) => {
          // extend the config service with this plugin spec and
          // collect its deprecations messages if some of its
          // settings are outdated
          const deprecations = [];
          await extendConfigService(spec, config, settings, (message) => {
            deprecations.push({ spec, message });
          });

          return {
            spec,
            deprecations,
          };
        }),
        // extend the config with all plugins before determining enabled status
        bufferAllResults,
        map(({ spec, deprecations }) => {
          const isRightVersion = spec.isVersionCompatible(config.get('pkg.version'));
          const enabled = isRightVersion && spec.isEnabled(config);
          return {
            config,
            spec,
            deprecations,
            enabledSpecs: enabled ? [spec] : [],
            disabledSpecs: enabled ? [] : [spec],
            invalidVersionSpecs: isRightVersion ? [] : [spec],
          };
        }),
        // determine which plugins are disabled before actually removing things from the config
        bufferAllResults,
        tap(result => {
          for (const spec of result.disabledSpecs) {
            disableConfigExtension(spec, config);
          }
        })
      )
    )),
    share()
  );

  return {
    // package JSONs found when searching configure paths
    packageJson$: packageJson$.pipe(
      mergeMap(result => (
        result.packageJson ? [result.packageJson] : []
      ))
    ),

    // plugin packs found when searching configured paths
    pack$: pack$.pipe(
      mergeMap(result => (
        result.pack ? [result.pack] : []
      ))
    ),

    // errors caused by invalid directories of plugin directories
    invalidDirectoryError$: pack$.pipe(
      mergeMap(result => (
        isInvalidDirectoryError(result.error) ? [result.error] : []
      ))
    ),

    // errors caused by directories that we expected to be plugin but were invalid
    invalidPackError$: pack$.pipe(
      mergeMap(result => (
        isInvalidPackError(result.error) ? [result.error] : []
      ))
    ),

    otherError$: pack$.pipe(
      mergeMap(result => (
        isUnhandledError(result.error) ? [result.error] : []
      ))
    ),

    // { spec, message } objects produced when transforming deprecated
    // settings for a plugin spec
    deprecation$: extendConfig$.pipe(
      mergeMap(result => result.deprecations)
    ),

    // the config service we extended with all of the plugin specs,
    // only emitted once it is fully extended by all
    extendedConfig$: extendConfig$.pipe(
      mergeMap(result => result.config),
      filter(Boolean),
      last()
    ),

    // all enabled PluginSpec objects
    spec$: extendConfig$.pipe(
      mergeMap(result => result.enabledSpecs)
    ),

    // all disabled PluginSpec objects
    disabledSpec$: extendConfig$.pipe(
      mergeMap(result => result.disabledSpecs)
    ),

    // all PluginSpec objects that were disabled because their version was incompatible
    invalidVersionSpec$: extendConfig$.pipe(
      mergeMap(result => result.invalidVersionSpecs)
    ),
  };
}
Example #15
0
/**
 * Returns a new Observable that multicasts (shares) the original Observable. As long as there is at least one
 * Subscriber this Observable will be subscribed and emitting data. When all subscribers have unsubscribed it will
 * unsubscribe from the source Observable. Because the Observable is multicasting it makes the stream `hot`.
 *
 * This behaves similarly to .publish().refCount(), with a behavior difference when the source observable emits complete.
 * .publish().refCount() will not resubscribe to the original source, however .share() will resubscribe to the original source.
 * Observable.of("test").publish().refCount() will not re-emit "test" on new subscriptions, Observable.of("test").share() will
 * re-emit "test" to new subscriptions.
 *
 * <img src="./img/share.png" width="100%">
 *
 * @return {Observable<T>} An Observable that upon connection causes the source Observable to emit items to its Observers.
 * @method share
 * @owner Observable
 */
function share() {
    return operators_1.share()(this);
}
    function setupDrawer() {
      const size$ = createXObservable(ResizeObserver)(drawerEl).pipe(
        map(
          () =>
            window.matchMedia(BREAK_POINT_DYNAMIC).matches
              ? LARGE_DESKTOP
              : window.matchMedia(BREAK_POINT_3).matches
                ? DESKTOP
                : MOBILE
        ),
        share(),
        startWith(
          window.matchMedia(BREAK_POINT_DYNAMIC).matches
            ? LARGE_DESKTOP
            : window.matchMedia(BREAK_POINT_3).matches
              ? DESKTOP
              : MOBILE
        )
      );

      // An observable keeping track of the drawer width.
      const drawerWidth$ = size$.pipe(
        map(size => (size >= LARGE_DESKTOP ? calcDrawerWidthDynamic() : calcDrawerWidth()))
      );

      // An observable keeping track of the distance between
      // the middle point of the screen and the middle point of the drawer.
      const dist$ = drawerWidth$.pipe(
        withLatestFrom(size$),
        map(
          ([drawerWidth, s]) =>
            s >= DESKTOP
              ? document.body.clientWidth / 2 - drawerWidth / 2
              : document.body.clientWidth / 2
        )
      );

      // An observable that keeps track of the range from where the drawer can be drawn.
      // Should be between 0 and the drawer's width on desktop; `getRange` on mobile.
      const range$ = drawerWidth$.pipe(
        withLatestFrom(size$),
        map(([drawerWidth, size]) => (size >= DESKTOP ? [0, drawerWidth] : getRange()))
      );

      // Sliding the drawer's content between the middle point of the screen,
      // and the middle point of the drawer when closed.
      Observable.create(observer => (drawerEl.moveCallback = x => observer.next(x)))
        .pipe(withLatestFrom(dist$, size$))
        .subscribe(([{ opacity }, dist, size]) => updateSidebar(size >= DESKTOP, dist, opacity));

      // Setting `will-change` at the beginning of an interaction, and remove at the end.
      drawerEl.addEventListener("hy-drawer-prepare", () => {
        if (hasCSSOM) {
          sidebar.attributeStyleMap.set("will-change", "transform");
          content.attributeStyleMap.set("will-change", "opacity");
        } else {
          sidebar.style.willChange = "transform";
          content.style.willChange = "opacity";
        }
      });

      drawerEl.addEventListener("hy-drawer-transitioned", () => {
        if (hasCSSOM) {
          sidebar.attributeStyleMap.delete("will-change");
          content.attributeStyleMap.delete("will-change");
        } else {
          sidebar.style.willChange = "";
          content.style.willChange = "";
        }
      });

      // Adding the click callback to the menu button.
      // Calling `preventDefault` in iOS Safari, because otherwise it's causing the navbar to appear,
      // which ruins the animation.
      menuEl.addEventListener("click", e => {
        if (isMobileSafari) e.preventDefault();
        window._drawer.toggle();
      });

      // Keeping track of the opened state.
      const opened$ = fromEvent(drawerEl, "hy-drawer-transitioned").pipe(
        map(e => e.detail),
        distinctUntilChanged(),
        tap(opened => !opened && removeIcon())
      );

      // Close the drawer on popstate, i.e. the back button.
      fromEvent(window, "popstate", { passive: true })
        .pipe(subscribeWhen(opened$))
        .subscribe(() => window._drawer.close());

      // Save scroll position before the drawer gets initialized.
      const scrollTop = window.pageYOffset || document.body.scrollTop;

      // Start the drawer in `opened` state when the cover class is present,
      // and the user hasn't started scrolling already.
      const opened = drawerEl.classList.contains("cover") && scrollTop <= 0;

      // HACK: uuuugly
      drawerEl._peek$ = size$.pipe(
        map(size => {
          switch (size) {
            case LARGE_DESKTOP:
              return calcDrawerWidthDynamic();
            case DESKTOP:
              return calcDrawerWidth();
            case MOBILE:
              return 0.5 * rem();
          }
        })
      );

      // We need the height of the darwer in case we need to reset the scroll position
      const drawerHeight = opened ? null : drawerEl.getBoundingClientRect().height;

      drawerEl.addEventListener(
        "hy-drawer-init",
        () => {
          // Show the icon indicating that the drawer can be drawn using touch gestures.
          setupIcon();

          // Add a class to incidate that the drawer has been initialized.
          drawerEl.classList.add("loaded");

          // Compensating for the change in layout after the drawer gets initialized.
          if (drawerHeight && scrollTop >= drawerHeight) {
            window.scrollTo(0, scrollTop - drawerHeight);
          }
        },
        { once: true }
      );

      dist$
        .pipe(
          withLatestFrom(size$),
          skip(1)
        )
        .subscribe(([dist, size]) =>
          updateSidebar(
            size >= DESKTOP,
            dist,
            typeof drawerEl.opacity !== "undefined" ? drawerEl.opacity : opened ? 1 : 0 // HACK
          )
        );

      // Now we create the component.
      window._drawer = defineWebComponent(drawerEl, opened);

      // Keeping the drawer updated.
      range$.subscribe(range => (drawerEl.range = range));
    }
Example #17
0
    return Rx.Observable.create(async observer => {
      const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-'));
      const chromiumArgs = args({
        userDataDir,
        viewport,
        verboseLogging: this.logger.isVerbose,
        disableSandbox: this.browserConfig.disableSandbox,
        proxyConfig: this.browserConfig.proxy,
      });

      let browser;
      let page;
      try {
        browser = await puppeteer.launch({
          userDataDir,
          executablePath: this.binaryPath,
          ignoreHTTPSErrors: true,
          args: chromiumArgs,
          env: {
            TZ: browserTimezone,
          },
        });

        page = await browser.newPage();

        // All navigation/waitFor methods default to 30 seconds,
        // which can cause the job to fail even if we bump timeouts in
        // the config. Help alleviate errors like
        // "TimeoutError: waiting for selector ".application" failed: timeout 30000ms exceeded"
        page.setDefaultTimeout(this.queueTimeout);
      } catch (err) {
        observer.error(new Error(`Error spawning Chromium browser: [${err}]`));
        throw err;
      }

      safeChildProcess(
        {
          async kill() {
            await browser.close();
          },
        },
        observer
      );

      // Register with a few useful puppeteer event handlers:
      // https://pptr.dev/#?product=Puppeteer&version=v1.10.0&show=api-event-error
      // https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page

      const stderr$ = Rx.fromEvent(page, 'console').pipe(
        filter(line => line._type === 'error'),
        map(line => line._text),
        share()
      );

      const [consoleMessage$, message$] = stderr$.pipe(
        partition(msg => msg.match(/\[\d+\/\d+.\d+:\w+:CONSOLE\(\d+\)\]/))
      );

      const driver$ = Rx.of(
        new HeadlessChromiumDriver(page, {
          maxScreenshotDimension: this.browserConfig.maxScreenshotDimension,
          logger: this.logger,
          inspect: this.browserConfig.inspect,
        })
      );

      const processError$ = Rx.fromEvent(page, 'error').pipe(
        mergeMap(err => Rx.throwError(new Error(`Unable to spawn Chromium: [${err}]`)))
      );

      const processPageError$ = Rx.fromEvent(page, 'pageerror').pipe(
        mergeMap(err => Rx.throwError(new Error(`Uncaught exception within the page: [${err}]`)))
      );

      const processRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe(
        mergeMap(req => {
          const failure = req.failure && req.failure();
          if (failure) {
            return Rx.throwError(
              new Error(`Request to [${req.url()}] failed! [${failure.errorText}]`)
            );
          }
          return Rx.throwError(new Error(`Unknown failure! [${JSON.stringify(req)}]`));
        })
      );

      const processExit$ = Rx.fromEvent(browser, 'disconnected').pipe(
        mergeMap(code =>
          Rx.throwError(new Error(`Chromium exited with: [${JSON.stringify({ code })}]`))
        )
      );

      const nssError$ = message$.pipe(
        filter(line => line.includes('error while loading shared libraries: libnss3.so')),
        mergeMap(() => Rx.throwError(new Error(`You must install nss for Reporting to work`)))
      );

      const fontError$ = message$.pipe(
        filter(line =>
          line.includes('Check failed: InitDefaultFont(). Could not find the default font')
        ),
        mergeMap(() =>
          Rx.throwError(new Error('You must install freetype and ttf-font for Reporting to work'))
        )
      );

      const noUsableSandbox$ = message$.pipe(
        filter(line => line.includes('No usable sandbox! Update your kernel')),
        mergeMap(() =>
          Rx.throwError(
            new Error(
              compactWhitespace(`
          Unable to use Chromium sandbox. This can be disabled at your own risk with
          'xpack.reporting.capture.browser.chromium.disableSandbox'
        `)
            )
          )
        )
      );

      const exit$ = Rx.merge(
        processError$,
        processPageError$,
        processRequestFailed$,
        processExit$,
        nssError$,
        fontError$,
        noUsableSandbox$
      );

      observer.next({
        driver$,
        consoleMessage$,
        message$,
        exit$,
      });

      // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium
      return () => {
        this.logger.debug(`deleting chromium user data directory at [${userDataDir}]`);
        // the unsubscribe function isn't `async` so we're going to make our best effort at
        // deleting the userDataDir and if it fails log an error.
        rimraf(userDataDir, err => {
          if (err) {
            return this.logger.error(
              `error deleting user data directory at [${userDataDir}]: [${err}]`
            );
          }
        });
      };
    });
Example #18
0
    return Rx.Observable.create(async observer => {
      const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-'));
      const chromiumArgs = args({
        userDataDir,
        viewport,
        verboseLogging: this.logger.isVerbose,
        disableSandbox: this.browserConfig.disableSandbox,
        proxyConfig: this.browserConfig.proxy,
      });

      let chromium;
      let page;
      try {
        chromium = await puppeteer.launch({
          userDataDir,
          executablePath: this.binaryPath,
          ignoreHTTPSErrors: true,
          args: chromiumArgs,
        });

        page = await chromium.newPage();
      } catch (err) {
        observer.error(new Error(`Caught error spawning Chromium`));
        return;
      }

      safeChildProcess({
        async kill() {
          await chromium.close();
        }
      }, observer);

      const stderr$ = Rx.fromEvent(page, 'console').pipe(
        filter(line => line._type === 'error'),
        map(line => line._text),
        share()
      );

      const [consoleMessage$, message$] = stderr$.pipe(
        partition(msg => msg.match(/\[\d+\/\d+.\d+:\w+:CONSOLE\(\d+\)\]/))
      );

      const driver$ = Rx.of(new HeadlessChromiumDriver(page, {
        maxScreenshotDimension: this.browserConfig.maxScreenshotDimension,
        logger: this.logger
      }));

      const processError$ = Rx.fromEvent(page, 'error').pipe(
        map((err) => this.logger.error(err)),
        mergeMap(() => Rx.throwError(new Error(`Unable to spawn Chromium`))),
      );

      const processExit$ = Rx.fromEvent(chromium, 'disconnected').pipe(
        mergeMap((err) => Rx.throwError(new Error(`Chromium exited with code: ${err}. ${JSON.stringify(err)}`)))
      );

      const nssError$ = message$.pipe(
        filter(line => line.includes('error while loading shared libraries: libnss3.so')),
        mergeMap(() => Rx.throwError(new Error(`You must install nss for Reporting to work`)))
      );

      const fontError$ = message$.pipe(
        filter(line => line.includes('Check failed: InitDefaultFont(). Could not find the default font')),
        mergeMap(() => Rx.throwError(new Error('You must install freetype and ttf-font for Reporting to work')))
      );

      const noUsableSandbox$ = message$.pipe(
        filter(line => line.includes('No usable sandbox! Update your kernel')),
        mergeMap(() => Rx.throwError(new Error(compactWhitespace(`
          Unable to use Chromium sandbox. This can be disabled at your own risk with
          'xpack.reporting.capture.browser.chromium.disableSandbox'
        `))))
      );

      const exit$ = Rx.merge(processError$, processExit$, nssError$, fontError$, noUsableSandbox$);

      observer.next({
        driver$,
        consoleMessage$,
        message$,
        exit$
      });

      // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium
      return () => {
        this.logger.debug(`deleting chromium user data directory at ${userDataDir}`);
        // the unsubscribe function isn't `async` so we're going to make our best effort at
        // deleting the userDataDir and if it fails log an error.
        rimraf(userDataDir, (err) => {
          if (err) {
            return this.logger.error(`error deleting user data directory at ${userDataDir}: ${err}`);
          }
        });
      };
    });
Example #19
0
 const newHandler = (argument, context) => {
     const argumentJson = stableStringify(argument);
     const maybeJob = runs.get(argumentJson);
     if (maybeJob) {
         return maybeJob;
     }
     const run = handler(argument, context).pipe(replayMessages ? operators_1.shareReplay() : operators_1.share());
     runs.set(argumentJson, run);
     return run;
 };
Example #20
0
    function setupDrawer() {
      const resize$ = fromEvent(window, "resize", { passive: true }).pipe(share());

      // An observable keeping track of whether the window size is greater than `BREAK_POINT_3`.
      const isDesktop$ = resize$.pipe(
        map(() => window.matchMedia(BREAK_POINT_3).matches),
        distinctUntilChanged(),
        share(),
        startWith(window.matchMedia(BREAK_POINT_3).matches)
      );

      // An observable keeping track of the drawer width.
      const drawerWidth$ = resize$.pipe(
        startWith({}),
        map(
          () =>
            window.matchMedia(BREAK_POINT_DYNAMIC).matches
              ? calcDrawerWidthDynamic()
              : calcDrawerWidth()
        )
      );

      // An observable keeping track of the distance between
      // the middle point of the screen and the middle point of the drawer.
      const dist$ = drawerWidth$.pipe(
        map(
          drawerWidth =>
            window.matchMedia(BREAK_POINT_3).matches
              ? document.body.clientWidth / 2 - drawerWidth / 2
              : document.body.clientWidth / 2
        )
      );

      // An observable that keeps track of the range from where the drawer can be drawn.
      // Should be between 0 and the drawer's width on desktop; `getRange` on mobile.
      const range$ = drawerWidth$.pipe(
        withLatestFrom(isDesktop$),
        map(([drawerWidth, isDesktop]) => (isDesktop ? [0, drawerWidth] : getRange()))
      );

      // Sliding the drawer's content between the middle point of the screen,
      // and the middle point of the drawer when closed.
      Observable.create(observer => (drawerEl.moveCallback = observer.next.bind(observer)))
        .pipe(withLatestFrom(dist$, isDesktop$))
        .subscribe(([{ opacity }, dist, isDesktop]) => updateSidebar(dist, opacity, isDesktop));

      // Setting `will-change` at the beginning of an interaction, and remove at the end.
      drawerEl.addEventListener("hy-drawer-prepare", () => {
        if (hasCSSOM) {
          sidebar.attributeStyleMap.set("will-change", "transform");
          sticky.attributeStyleMap.set("will-change", "opacity");
        } else {
          sidebar.style.willChange = "transform";
          sticky.style.willChange = "opacity";
        }
      });

      drawerEl.addEventListener("hy-drawer-transitioned", () => {
        if (hasCSSOM) {
          sidebar.attributeStyleMap.delete("will-change");
          sticky.attributeStyleMap.delete("will-change");
        } else {
          sidebar.style.willChange = "";
          sticky.style.willChange = "";
        }
      });

      // Adding the click callback to the menu button.
      // Calling `preventDefault` in iOS Safari, because otherwise it's causing the navbar to appear,
      // which ruins the animation.
      menuEl.addEventListener("click", e => {
        if (isMobileSafari) e.preventDefault();
        window._drawer.toggle();
      });

      // Keeping track of the opened state.
      const opened$ = fromEvent(drawerEl, "hy-drawer-transitioned").pipe(
        map(e => e.detail),
        distinctUntilChanged(),
        tap(opened => !opened && removeIcon())
      );

      // Close the drawer on popstate, i.e. the back button.
      fromEvent(window, "popstate", { passive: true })
        .pipe(subscribeWhen(opened$))
        .subscribe(() => window._drawer.close());

      // Save scroll position before the drawer gets initialized.
      const scrollTop = window.pageYOffset || document.body.scrollTop;

      // Start the drawer in `opened` state when the cover class is present,
      // and the user hasn't started scrolling already.
      const opened = drawerEl.classList.contains("cover") && scrollTop <= 0;

      // We need the height of the darwer in case we need to reset the scroll position
      let drawerHeight;
      if (!opened) {
        drawerHeight = drawerEl.getBoundingClientRect().height;
      }

      drawerEl.addEventListener(
        "hy-drawer-init",
        () => {
          // Keeping the drawer updated.
          range$.subscribe(range => (drawerEl.range = range));

          // Show the icon indicating that the drawer can be drawn using touch gestures.
          setupIcon();

          // Add a class to incidate that the drawer has been initialized.
          drawerEl.classList.add("loaded");

          // The drawer height is `100vh` before the drawer is initialized and is now set to 0.
          // We remove `innerHeight` from the old scroll position to prevent the content form "jumping".
          if (!opened && scrollTop >= drawerHeight) {
            window.scrollTo(0, scrollTop - drawerHeight);
          }
        },
        { once: true }
      );

      // HACK
      let firstRun = true;
      dist$.pipe(withLatestFrom(isDesktop$)).subscribe(([dist, isDesktop]) => {
        if (firstRun) {
          firstRun = false;
          updateSidebar(dist, opened ? 1 : 0, isDesktop);
        } else {
          updateSidebar(dist, drawerEl.opacity, isDesktop);
        }
      });

      // Now we create the component.
      window._drawer = defineWebComponent(drawerEl, opened);
    }
async function open(deviceOrId: Device | string, needsReconnect: boolean) {
  let device;
  if (typeof deviceOrId === "string") {
    if (transportsCache[deviceOrId]) {
      logSubject.next({
        type: "verbose",
        message: "Transport in cache, using that."
      });
      return transportsCache[deviceOrId];
    }

    logSubject.next({ type: "verbose", message: `open(${deviceOrId})` });

    await awaitsBleOn(bleManager);

    if (!device) {
      // works for iOS but not Android
      const devices = await bleManager.devices([deviceOrId]);
      logSubject.next({
        type: "verbose",
        message: `found ${devices.length} devices`
      });
      [device] = devices;
    }

    if (!device) {
      const connectedDevices = await bleManager.connectedDevices(
        getBluetoothServiceUuids()
      );
      const connectedDevicesFiltered = connectedDevices.filter(
        d => d.id === deviceOrId
      );
      logSubject.next({
        type: "verbose",
        message: `found ${connectedDevicesFiltered.length} connected devices`
      });
      [device] = connectedDevicesFiltered;
    }

    if (!device) {
      logSubject.next({
        type: "verbose",
        message: `connectToDevice(${deviceOrId})`
      });
      try {
        device = await bleManager.connectToDevice(deviceOrId, connectOptions);
      } catch (e) {
        if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
          connectOptions = {};
          device = await bleManager.connectToDevice(deviceOrId);
        } else {
          throw e;
        }
      }
    }

    if (!device) {
      throw new CantOpenDevice();
    }
  } else {
    device = deviceOrId;
  }

  if (!(await device.isConnected())) {
    logSubject.next({
      type: "verbose",
      message: "not connected. connecting..."
    });
    try {
      await device.connect(connectOptions);
    } catch (e) {
      if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
        connectOptions = {};
        await device.connect();
      } else {
        throw e;
      }
    }
  }

  await device.discoverAllServicesAndCharacteristics();

  let res = retrieveInfos(device);
  let characteristics;
  if (!res) {
    for (const uuid of getBluetoothServiceUuids()) {
      try {
        characteristics = await device.characteristicsForService(uuid);
        res = getInfosForServiceUuid(uuid);
        break;
      } catch (e) {
        // we attempt to connect to service
      }
    }
  }
  if (!res) {
    throw new TransportError("service not found", "BLEServiceNotFound");
  }

  const { deviceModel, serviceUuid, writeUuid, notifyUuid } = res;

  if (!characteristics) {
    characteristics = await device.characteristicsForService(serviceUuid);
  }

  if (!characteristics) {
    throw new TransportError("service not found", "BLEServiceNotFound");
  }
  let writeC;
  let notifyC;
  for (const c of characteristics) {
    if (c.uuid === writeUuid) {
      writeC = c;
    } else if (c.uuid === notifyUuid) {
      notifyC = c;
    }
  }
  if (!writeC) {
    throw new TransportError(
      "write characteristic not found",
      "BLEChracteristicNotFound"
    );
  }
  if (!notifyC) {
    throw new TransportError(
      "notify characteristic not found",
      "BLEChracteristicNotFound"
    );
  }
  if (!writeC.isWritableWithResponse) {
    throw new TransportError(
      "write characteristic not writableWithResponse",
      "BLEChracteristicInvalid"
    );
  }
  if (!notifyC.isNotifiable) {
    throw new TransportError(
      "notify characteristic not notifiable",
      "BLEChracteristicInvalid"
    );
  }

  logSubject.next({ type: "verbose", message: `device.mtu=${device.mtu}` });

  const notifyObservable = monitorCharacteristic(notifyC).pipe(
    tap(value => {
      logSubject.next({
        type: "ble-frame-read",
        message: value.toString("hex")
      });
    }),
    share()
  );

  const notif = notifyObservable.subscribe();

  const transport = new BluetoothTransport(
    device,
    writeC,
    notifyObservable,
    deviceModel
  );

  const onDisconnect = e => {
    transport.notYetDisconnected = false;
    notif.unsubscribe();
    disconnectedSub.remove();
    delete transportsCache[transport.id];
    logSubject.next({
      type: "verbose",
      message: `BleTransport(${transport.id}) disconnected`
    });
    transport.emit("disconnect", e);
  };

  transportsCache[transport.id] = transport;
  const disconnectedSub = device.onDisconnected(e => {
    if (!transport.notYetDisconnected) return;
    onDisconnect(e);
  });

  let beforeMTUTime = Date.now();
  try {
    await transport.inferMTU();
  } finally {
    let afterMTUTime = Date.now();

    if (reconnectionConfig) {
      // workaround for #279: we need to open() again if we come the first time here,
      // to make sure we do a disconnect() after the first pairing time
      // because of a firmware bug

      if (afterMTUTime - beforeMTUTime < reconnectionConfig.pairingThreshold) {
        needsReconnect = false; // (optim) there is likely no new pairing done because mtu answer was fast.
      }

      if (needsReconnect) {
        // necessary time for the bonding workaround
        await BluetoothTransport.disconnect(transport.id).catch(() => {});
        await delay(reconnectionConfig.delayAfterFirstPairing);
      }
    } else {
      needsReconnect = false;
    }
  }

  if (needsReconnect) {
    return open(device, false);
  }

  return transport;
}
Example #22
0
export function fromRef(ref) {
    return _fromRef(ref).pipe(share());
}
Example #23
0
 bindMessageHandlers(client, handlers, transform) {
     const close$ = rxjs_1.fromEvent(client, 'close').pipe(operators_1.share(), operators_1.first());
     const source$ = rxjs_1.fromEvent(client, 'message').pipe(operators_1.mergeMap(data => this.bindMessageHandler(data, handlers, transform).pipe(operators_1.filter(result => result))), operators_1.takeUntil(close$));
     source$.subscribe(response => client.send(JSON.stringify(response)));
 }
Example #24
0
 /**
  * Returns an Observable which produces the string contents of the given URL. Results may be
  * cached, so future calls with the same URL may not cause another HTTP request.
  * @param {?} safeUrl
  * @return {?}
  */
 _fetchUrl(safeUrl) {
     if (!this._httpClient) {
         throw getMatIconNoHttpProviderError();
     }
     if (safeUrl == null) {
         throw Error(`Cannot fetch icon from URL "${safeUrl}".`);
     }
     const /** @type {?} */ url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl);
     if (!url) {
         throw getMatIconFailedToSanitizeUrlError(safeUrl);
     }
     // Store in-progress fetches to avoid sending a duplicate request for a URL when there is
     // already a request in progress for that URL. It's necessary to call share() on the
     // Observable returned by http.get() so that multiple subscribers don't cause multiple XHRs.
     const /** @type {?} */ inProgressFetch = this._inProgressUrlFetches.get(url);
     if (inProgressFetch) {
         return inProgressFetch;
     }
     // TODO(jelbourn): for some reason, the `finalize` operator "loses" the generic type on the
     // Observable. Figure out why and fix it.
     const /** @type {?} */ req = this._httpClient.get(url, { responseType: 'text' }).pipe(finalize(() => this._inProgressUrlFetches.delete(url)), share());
     this._inProgressUrlFetches.set(url, req);
     return req;
 }