@rxjs-ninja/rxjs-utility
Version:
Useful utilities for RxJS
1,633 lines (1,463 loc) • 60.7 kB
JavaScript
import { debounceTime, distinctUntilChanged, switchMap, map, withLatestFrom, scan, takeWhile, takeUntil, catchError, tap, mergeMap, finalize } from 'rxjs/operators';
import { isObservable, of, Observable, Subject, fromEvent, from, EMPTY, throwError, defer } from 'rxjs';
/**
* @packageDocumentation
* @module Utility
*/
/**
* Returns an Observable value from a remote source where the [[QueryMethod]] returns a result such as a search from
* a remote location
*
* @category Mapping
*
* @param time The debounce time before the query method is executed
* @param queryMethod The method that returns the search
*
* @typeParam T The response from an API which returns the result of a query
*
* @example
* On keypress of an input pass value and debounce for `500ms` before sending to remote server request
* ```ts
* const doQuery = (query: string) => http.get(`/search?query=${query}`);
* fromEvent(input, 'keyup').pipe(map(event => event.target.value), debounceWithQuery(500, doQuery)).subscribe();
* ```
* Output: `A valid server response containing search results`
*
* @returns An Observable that emits a value from a server request
*/
function debounceWithQuery(time, queryMethod) {
return function (source) {
return source.pipe(debounceTime(time), distinctUntilChanged(), switchMap(queryMethod));
};
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* JWT Decoder from
* https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
* @private
* @internal
* @param token
*/
function parseJwt(token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
/**
* Returns an Observable that emits an object from a parsed JWT token
*
* @category Mapping
*
* @typeParam T The known JWT response object
*
* @example Parse a JWT token and return the decoded body
* ```ts
* const input =
* 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
* of(input).pipe(decodeJWT()).subscribe()
* ```
* Output: `{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022}`
*
* @returns Observable that emits a decoded JWT token body
*/
function decodeJWT() {
return function (source) {
return source.pipe(map(function (value) {
return parseJwt(value);
}));
};
}
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* @private
* @internal
* @param input
*/
function createOrReturnObservable(input) {
return isObservable(input) ? input : of(input);
}
/**
* @private
* @param num
* @param places
*/
function roundNumber(num, places) {
var factor = Math.pow(10, places);
return Math.round(num * factor) / factor;
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* @private
* @internal
*/
var SupportedLengths = {
/**
* Centimeters
*/
CENTIMETERS: 'cm',
/**
* Feet
*/
FEET: 'feet',
/**
* Inches
*/
INCHES: 'inches',
/**
* Kilometers
*/
KILOMETERS: 'km',
/**
* Meters
*/
METERS: 'meters',
/**
* Miles
*/
MILES: 'miles',
/**
* Yards
*/
YARDS: 'yards'
};
/**
* Supported length types in the [[length]] operator
*/
var Lengths;
(function (Lengths) {
/**
* Centimeters
*/
Lengths["CENTIMETERS"] = "cm";
/**
* Feet
*/
Lengths["FEET"] = "feet";
/**
* Inches
*/
Lengths["INCHES"] = "inches";
/**
* Kilometers
*/
Lengths["KILOMETERS"] = "km";
/**
* Meters
*/
Lengths["METERS"] = "meters";
/**
* Miles
*/
Lengths["MILES"] = "miles";
/**
* Yards
*/
Lengths["YARDS"] = "yards";
})(Lengths || (Lengths = {}));
var _a, _b, _c, _d, _e, _f, _g;
/**
* Contains conversion functions for values from centimeters
* @private
*/
var fromCm = (_a = {}, _a[SupportedLengths.CENTIMETERS] = function (num, precision) {
return roundNumber(num, precision);
}, _a[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value * 0.032808, precision);
}, _a[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value * 0.3937, precision);
}, _a[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value / 100000, precision);
}, _a[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value / 100, precision);
}, _a[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value * 0.0000062137, precision);
}, _a.yards = function (value, precision) {
return roundNumber(value * 0.010936, precision);
}, _a);
/**
* Contains conversion functions for values from feet
* @private
*/
var fromFeet = (_b = {}, _b[SupportedLengths.CENTIMETERS] = function (value, precision) {
return roundNumber(value / 0.032808, precision);
}, _b[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value, precision);
}, _b[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value * 12, precision);
}, _b[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value / 3280.8, precision);
}, _b[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value / 3.2808, precision);
}, _b[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value * 0.00018939, precision);
}, _b[SupportedLengths.YARDS] = function (value, precision) {
return roundNumber(value * 0.33333, precision);
}, _b);
/**
* Contains conversion functions for values from meters
* @private
*/
var fromInches = (_c = {}, _c[SupportedLengths.CENTIMETERS] = function (value, precision) {
return roundNumber(value / 0.3937, precision);
}, _c[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value * 0.083333, precision);
}, _c[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value, precision);
}, _c[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value / 39370, precision);
}, _c[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value / 39.37, precision);
}, _c[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value * 0.000015783, precision);
}, _c[SupportedLengths.YARDS] = function (value, precision) {
return roundNumber(value * 0.027778, precision);
}, _c);
/**
* Contains conversion functions for values from centimeters
* @private
*/
var fromKm = (_d = {}, _d[SupportedLengths.CENTIMETERS] = function (value, precision) {
return roundNumber(value * 100000, precision);
}, _d[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value * 3280.8, precision);
}, _d[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value * 39370, precision);
}, _d[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value, precision);
}, _d[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value * 1000, precision);
}, _d[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value * 0.62137, precision);
}, _d[SupportedLengths.YARDS] = function (value, precision) {
return roundNumber(value * 1093.6, precision);
}, _d);
/**
* Contains conversion functions for values from meters
* @private
*/
var fromMeters = (_e = {}, _e[SupportedLengths.CENTIMETERS] = function (value, precision) {
return roundNumber(value / 0.01, precision);
}, _e[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value * 3.2808, precision);
}, _e[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value * 39.37, precision);
}, _e[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value / 1000, precision);
}, _e[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value, precision);
}, _e[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value * 0.00062137, precision);
}, _e[SupportedLengths.YARDS] = function (value, precision) {
return roundNumber(value * 1.0936, precision);
}, _e);
/**
* Contains conversion functions for values from centimeters
* @private
*/
var fromMiles = (_f = {}, _f[SupportedLengths.CENTIMETERS] = function (value, precision) {
return roundNumber(value / 0.0000062137, precision);
}, _f[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value * 5280, precision);
}, _f[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value * 63360, precision);
}, _f[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value / 0.62137, precision);
}, _f[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value / 0.00062137, precision);
}, _f[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value, precision);
}, _f[SupportedLengths.YARDS] = function (value, precision) {
return roundNumber(value * 1760, precision);
}, _f);
/**
* Contains conversion functions for values from centimeters
* @private
*/
var fromYards = (_g = {}, _g[SupportedLengths.CENTIMETERS] = function (value, precision) {
return roundNumber(value / 0.010936, precision);
}, _g[SupportedLengths.FEET] = function (value, precision) {
return roundNumber(value * 3, precision);
}, _g[SupportedLengths.INCHES] = function (value, precision) {
return roundNumber(value * 36, precision);
}, _g[SupportedLengths.KILOMETERS] = function (value, precision) {
return roundNumber(value / 1093.6, precision);
}, _g[SupportedLengths.METERS] = function (value, precision) {
return roundNumber(value / 1.0936, precision);
}, _g[SupportedLengths.MILES] = function (value, precision) {
return roundNumber(value * 0.00056818, precision);
}, _g[SupportedLengths.YARDS] = function (value, precision) {
return roundNumber(value, precision);
}, _g);
/**
* Returns an Observable that converts the source value through [[Lengths]] conversion
*
* @category Conversion
*
* @param fromLength The length type of the source value
* @param toLength The length type of the output value
* @param precision The number of decimal places to return, default is `3`
*
* @example
* Convert a value from Miles to Meters
* ```ts
* const source$ = from([100, 200, 300, 500]);
*
* source$.pipe(length(Lengths.MILES, Lengths.METERS, 0)).subscribe()
* ```
* Output: `160934, 321869, 482803, 804672`
*
* @example
* Convert a value from Inches to Yards to precision `2`
* ```ts
* const source$ = from([100, 200, 300, 500]);
*
* source$.pipe(length('inches', 'yards', 2)).subscribe()
* ```
* Output: `2.78, 5.56, 8.33, 13.89`
*
* @returns Observable that emits a number that is the `from` [[Lengths]] converted to the `to` [[Lengths]]
*/
function length(fromLength, toLength, precision) {
if (precision === void 0) {
precision = 3;
}
var fromLength$ = createOrReturnObservable(fromLength);
var toLength$ = createOrReturnObservable(toLength);
var precision$ = createOrReturnObservable(precision);
return function (source) {
return source.pipe(withLatestFrom(fromLength$, toLength$, precision$), map(function (_a) {
var _b = __read(_a, 4),
value = _b[0],
fromLengthValue = _b[1],
toLengthValue = _b[2],
precisionValue = _b[3];
switch (fromLengthValue) {
case SupportedLengths.CENTIMETERS:
{
return fromCm[toLengthValue](value, precisionValue);
}
case SupportedLengths.FEET:
{
return fromFeet[toLengthValue](value, precisionValue);
}
case SupportedLengths.INCHES:
{
return fromInches[toLengthValue](value, precisionValue);
}
case SupportedLengths.KILOMETERS:
{
return fromKm[toLengthValue](value, precisionValue);
}
case SupportedLengths.METERS:
{
return fromMeters[toLengthValue](value, precisionValue);
}
case SupportedLengths.MILES:
{
return fromMiles[toLengthValue](value, precisionValue);
}
case SupportedLengths.YARDS:
{
return fromYards[toLengthValue](value, precisionValue);
}
/* istanbul ignore next-line */
default:
return roundNumber(value, precisionValue);
}
}));
};
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Returns an Observable that emits values from an EventSource subscribing to the the passed
* `eventName` stream. Takes an optional Observer (e.g. Subject) to emit when the stream opens
*
* @category Streams
*
* @see {@link https://codesandbox.io/s/rxjs-ninja-eventsource-example-w7v4j|RxJS Event Source Example}
*
* @typeParam T The type of the value in the message `data` property
*
* @param source The event source to subscribe to
* @param eventName The name of the event to listen to, by default this is `message`
* @param openObserver Optional observer that is emitted when the event source is opened
* @param signal Optional signal to end the event source
*
* @example
* Subscribe to an EventSource, listen for it opening and provide a stop signal
* ```ts
* // The event source emits a time every 1 minute
* const eventSource = new EventSource('/event-stream');
* const stopSource = new AbortController();
* const isOpen$ = new Subject<Event>();
*
* function endSource() {
* stopSource.abort();
* }
*
* fromEventSource<string>(eventSource, 'message', isOpen$, stopSource.signal).pipe(
* tap(value => {
* const parsed = JSON.parse(value);
* outputSpan.innerHTML = `The time is ${parsed.message}`
* }),
* finalize(() => {
* outputSpan.innerHTML = `EventSource closed`
* })
* ).subscribe();
* ```
* Output: `'The time is 12:01', 'The time is 12:02', ....`
*
* @returns Observable that emits the `data` value from an EventSource message
*/
function fromEventSource(source, eventName, openObserver, signal) {
if (eventName === void 0) {
eventName = 'message';
}
return new Observable(function (subscriber) {
if (signal) {
signal.onabort = function () {
source.close();
!subscriber.closed && subscriber.complete();
};
}
/**
* @private
* @internal
* @param event
*/
function handleMessage(event) {
if (!eventName || eventName && eventName === event.type) {
subscriber.next(event.data);
}
}
/**
* @private
* @internal
* @param event
*/
/* istanbul ignore next */
function handleError(event) {
subscriber.error(event);
}
/**
* @private
* @internal
* @param event
*/
/* istanbul ignore next */
function handleOpen(event) {
openObserver === null || openObserver === void 0 ? void 0 : openObserver.next(event);
openObserver === null || openObserver === void 0 ? void 0 : openObserver.complete();
source.removeEventListener('open', handleOpen);
}
/* istanbul ignore next */
if (openObserver) {
source.addEventListener('open', handleOpen);
}
source.addEventListener(eventName, handleMessage);
source.addEventListener('error', handleError);
return function () {
source.removeEventListener(eventName, handleMessage);
source.removeEventListener('error', handleError);
source.removeEventListener('open', handleOpen);
source.close();
/* istanbul ignore next */
!subscriber.closed && subscriber.complete();
};
});
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Creates an Observable source from a ReadableStream source that will emit any
* values emitted by the stream.
*
* @category Streams
*
* @see {@link https://stackblitz.com/edit/rxjs-readable-stream|StreamAPI Number Stream}
* @see {@link https://stackblitz.com/edit/rxjs-readable-stream-fetch|Fetch + StreamAPI Demo}
*
* @param stream The ReadableStream to subscribe to
* @param signal Optional {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal|AbortSignal} to provide
* to the underlying stream
* @param queueStrategy Optional strategy for backpressure queueing
* @param throwEndAsError Optional to return an error when the `AbortSignal` has been fired instead of just closing
*
* @example Create a ReadableStream of `0` to `100` and convert to an Observable
* ```ts
* const stream = new ReadableStream({
* start: (controller) => {
* for (let i = 0; i <100; i++) {
* controller.enqueue(i)
* }
* controller.close();
* }
* });
*
* fromReadableStream(stream).pipe(reduce((a, b) => a + b)).subscribe();
* ```
* Output: `4950`
*
* @returns Observable that emits from a ReadableStream source
*/
function fromReadableStream(stream, signal, queueStrategy, throwEndAsError) {
if (throwEndAsError === void 0) {
throwEndAsError = false;
}
/**
* @private
* @internal
* @param subscriber
*/
function createStream(subscriber) {
return new WritableStream({
write: function write(value) {
return subscriber.next(value);
},
abort: function abort(error) {
if (throwEndAsError) {
subscriber.error(error);
/* istanbul ignore next-line */
} else if (!subscriber.closed) {
subscriber.complete();
}
},
close: function close() {
/* istanbul ignore next-line */
if (!subscriber.closed) {
subscriber.complete();
}
}
}, queueStrategy);
}
return new Observable(function (subscriber) {
stream.pipeTo(createStream(subscriber), {
signal: signal
}).then(function () {
/* istanbul ignore next-line */
return !subscriber.closed && subscriber.complete();
})["catch"](function (error) {
return subscriber.error(error);
});
return function () {
return !stream.locked && stream.cancel();
};
});
}
/**
* Returns an Observable from {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API|fetch} that emits
* a number during the progress of the file download and once finished emits a
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array|Uint8Array}
*
* @category HTTP
*
* @see {@link https://stackblitz.com/edit/fetch-with-progress|Demo Image Loader}
*
* @param input A `string` url or {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request}
* object
* @param init Optional `RequestInit` dictionary
* @param controller Optional {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController|AbortController}
* used to cancel any outstanding requests
*
* @example Set up fetching a large image, show a progress and image on final load
* ```ts
* const image = document.querySelector(".image") as HTMLImageElement;
* const progress = document.querySelector(".progress") as HTMLSpanElement;
*
* progress.innerHTML = "0%";
*
* fromFetchWithProgress("https://example.com/large-image.jpg").pipe(
* tap(val => {
* if (typeof val === "number") {
* progress.innerHTML = `${Math.round(val * 100)}%`;
* }
* }),
* filter(val => val instanceof Uint8Array),
* tap((val: Uint8Array) => {
* const img = URL.createObjectURL(
* new Blob([val.buffer], { type: "image/png" })
* );
* image.src = img;
* }),
* catchError(error => {
* progress.innerHTML = error;
* return throwError(undefined);
* })).subscribe();
* ```
*/
function fromFetchWithProgress(input, init, controller) {
var _this = this;
if (init === void 0) {
init = {};
}
if (controller === void 0) {
controller = new AbortController();
}
var signal = controller.signal;
var data$ = new Subject();
return new Observable(function (subscriber) {
fetch(input, __assign(__assign({}, init), {
signal: signal
})).then(function (_a) {
var body = _a.body,
headers = _a.headers,
ok = _a.ok,
status = _a.status,
statusText = _a.statusText;
return __awaiter(_this, void 0, void 0, function () {
var reader, total, _process;
var _this = this;
return __generator(this, function (_b) {
if (!body) {
throw new Error('Response body is empty');
}
if (!ok) {
throw new Error(status + ": " + statusText);
}
reader = body.getReader();
total = Number(headers.get('content-length'));
data$.pipe(scan(function (acc, _a) {
var done = _a.done,
_b = _a.value,
value = _b === void 0 ? [] : _b;
return done ? Uint8Array.from(__spread(acc, value)) : __spread(acc, value);
}, []), map(function (value) {
return value instanceof Uint8Array ? value : value.length / (total === 0 ? value.length : total);
}), takeWhile(function (result) {
return typeof result === 'number';
}, true), takeUntil(fromEvent(signal, 'abort'))).subscribe(subscriber);
_process = function process(result) {
return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
data$.next(result);
return [2
/*return*/
, !result.done ? reader.read().then(_process) : Promise.resolve(result)];
});
});
};
return [2
/*return*/
, reader.read().then(_process)];
});
});
})["catch"](function (error) {
return subscriber.error(error);
});
return function () {
return controller && controller.abort();
};
});
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Returns the source Observable, emitting it through the passed WritableStream and handling the internal
* subscription state and error handling. If passed an
* {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal|AbortSignal} the WritableStream can be ended
* early without ending the entire subscription
*
* @category Streams
*
* @see {@link https://stackblitz.com/edit/rxjs-writable-stream|Writable Stream Demo}
*
* @param stream The Writer object to emit the data to
* @param signal Optional signal used to end the writer without ending the rest of the stream
*
* @example Write an array of Observable values to a WritableStream
* ```ts
* let result = ''
* const stream = new WritableStream({
* write: (chunk) => result += chunk,
* close: () => console.log(result)
* });
*
* const input = ['Hello', ' ', 'RxJS', ' ', 'Ninja'];
* from(input).pipe(toWritableStream(stream)).subscribe();
* ```
* Output: `Hello RxJS Ninja`
*
* @returns Observable that emits the source observable after performing a write to the WritableStream
*/
function toWritableStream(stream, signal) {
var _a; // Here we check if there is a getWriter method to support WritableStreamDefaultWriter
// eslint-disable-next-line
var writer = ((_a = stream) === null || _a === void 0 ? void 0 : _a.getWriter) ? stream.getWriter() : stream;
var closed = false; // If there is a signal passed add a handler for the abort method and attempt to close the writer
if (signal) {
signal.onabort = function () {
closed = true;
from(writer.close()).pipe(catchError(function () {
return EMPTY;
})).subscribe();
};
}
return function (source) {
return source.pipe(tap(function (value) {
// Attempt to write to the writer is not closed, if there is an error don't pass it on
if (!closed) {
from(writer.ready).pipe(mergeMap(function () {
return from(writer.write(value)).pipe(catchError(function () {
return EMPTY;
}));
})).subscribe();
}
}), catchError(function (error) {
closed = true; // Attempt to close the writer then always return the original error
/* istanbul ignore next */
return from(writer.close()).pipe(switchMap(function () {
return throwError(error);
}));
}), finalize(function () {
closed = true; // Attempt to close any open writer, don't emit any error
from(writer.close()).pipe(catchError(function () {
return EMPTY;
})).subscribe();
}));
};
}
/**
* Returns an Observable that emits the response from a source connected to via the
* {@link https://reillyeon.github.io/serial|Web Serial API}. The function can also accept an Observable
* that emits values to write to the serial device, allowing two-way communication.
*
* Both the input and output values must be
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array|Uint8Array}, you
* can use {@link https://developer.mozilla.org/en-us/docs/Web/API/TextEncoder|TextEncoder} and
* {@link https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder|TextDecoder} to convert between strings, which
* can be seen in the demo
*
* The function will also handle opening and closing of the port from the serial device when using an `AbortSignal` or
* ending the RxJS subscription.
*
* @category Streams
*
* @remarks Web Serial is available in Chrome or Edge 89 or later, in earlier versions it can be enabled using the
* experimental web features flag. To use the feature is **must** be invoked with a user action such as a user
* button click, and in a browser location that provides an acceptable policy before the port can be opened by this
* operator.
*
* @see {@link https://rxjs-from-web-serial.stackblitz.io|RxJS Web Serial Demo}
* @see {@link https://stackblitz.com/edit/rxjs-from-web-serial|Demo Source}
*
* @param port The SerialPort object to connect to
* @param writerSource Optional Observable source to emit values to the serial connection writer
* @param options Options for the connection - if none passed a default `baudRate` of `9600` is set
* @param signal Optional signal to end the source
*
* @returns Observable that emits the output from a serial source
*/
function fromWebSerial(port, writerSource, options, signal) {
var _this = this;
return new Observable(function (subscriber) {
var closeStreams$ = new Subject();
var reader;
var writer; // Allow for undefined options
from(port.open(options || {
baudRate: 9600
})).pipe(tap(function () {
/**
* The next block deals specifically with handling the closing of all internal and external streams
*/
closeStreams$.asObservable().pipe(tap(function () {
return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
return [4
/*yield*/
, writer.close()];
case 1:
_a.sent();
return [4
/*yield*/
, reader.cancel()];
case 2:
_a.sent();
return [4
/*yield*/
, port.close()];
case 3:
_a.sent();
!subscriber.closed && subscriber.complete();
return [2
/*return*/
];
}
});
});
})).subscribe();
if (signal) {
signal.onabort = function () {
closeStreams$.next();
closeStreams$.complete();
};
}
port.ondisconnect = function () {
closeStreams$.next();
closeStreams$.complete();
};
/**
* Set up the writer to the serial device
*/
if (writerSource && port.writable) {
writer = port.writable.getWriter();
writerSource.pipe(takeUntil(closeStreams$), toWritableStream(writer), catchError(function (err) {
subscriber.error(err);
return EMPTY;
})).subscribe();
}
/**
* Loop over the promise response from the reader until it's done or the port is no longer readable
* @private
* @internal
* @param result
*/
var process = function process(result) {
return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
subscriber.next(result.value);
return [2
/*return*/
, !result.done || !port.readable ? reader.read().then(process) : Promise.resolve(result)];
});
});
};
if (port.readable) {
reader = port.readable.getReader();
from(reader.read()).pipe(takeUntil(closeStreams$), switchMap(function (result) {
return process(result);
})).subscribe();
}
}), catchError(function (err) {
subscriber.error(err);
return EMPTY;
})).subscribe();
return function () {
closeStreams$.next();
closeStreams$.complete();
};
});
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Takes an input string of rgba(num, num, num, num) and converts it to a hex
* @param input The input RGBA
* @private
* @internal
* @returns A tuple containing the
*/
function convertRGBAStrToHexComponents(input) {
var values = input.replace(/\s/g, '').match(/^rgba?\((\d+),(\d+),(\d+),?([^,\s)]+)?/i);
if (!values) {
throw new Error('No valid RGBA value to parse');
} // eslint-disable-next-line @typescript-eslint/no-unused-vars
var _a = __read(values, 5),
_ = _a[0],
red = _a[1],
green = _a[2],
blue = _a[3],
alpha = _a[4];
var alphaNum = Math.round(Math.round(Number(alpha ? alpha.trim() : 1) * 100) / 100 * 255);
var hexAlpha = (alphaNum + 0x10000).toString(16).substr(-2);
return [(Number(red) | 1 << 8).toString(16).slice(1), (Number(green) | 1 << 8).toString(16).slice(1), (Number(blue) | 1 << 8).toString(16).slice(1), hexAlpha];
}
/**
* @private
* @internal
* @param hex
*/
var isValidHex = function isValidHex(hex) {
return /^#([A-Fa-f0-9]{3,4}){1,2}$/.test(hex);
};
/**
* @private
* @internal
* @param st
* @param chunkSize
*/
var getChunksFromString = function getChunksFromString(st, chunkSize) {
return st.match(new RegExp(".{" + chunkSize + "}", 'g'));
};
/**
* @private
* @internal
* @param hexStr
*/
var convertHexUnitTo256 = function convertHexUnitTo256(hexStr) {
return parseInt(hexStr.repeat(2 / hexStr.length), 16);
};
/**
* @private
* @internal
*/
var getAlphafloat = function getAlphafloat(a, alpha) {
if (typeof a !== 'undefined') {
return a / 255;
}
return alpha && alpha >= 0 && alpha <= 1 ? alpha : 1;
};
/**
* @private
* @internal
* @param hex
* @param alpha
*/
function convertHexToRGBA(hex, alpha) {
if (!isValidHex(hex)) {
throw new Error('Invalid HEX');
}
var chunkSize = Math.floor((hex.length - 1) / 3);
var hexArr = getChunksFromString(hex.slice(1), chunkSize);
/* istanbul ignore next-line */
if (!hexArr) {
throw new Error('No chunks from hex string');
}
var _a = __read(hexArr.map(convertHexUnitTo256), 4),
r = _a[0],
g = _a[1],
b = _a[2],
a = _a[3];
if (typeof a === 'undefined' && typeof alpha === 'undefined') {
return "rgb(" + r + ", " + g + ", " + b + ")";
}
var roundedFloat = Math.round((getAlphafloat(a, alpha) + Number.EPSILON) * 100) / 100;
return "rgba(" + r + ", " + g + ", " + b + ", " + roundedFloat + ")";
}
/**
* Returns an Observable that emits a string containing a `rgb` or `rgba` colour (if `alpha` is included in the sting
* or as a property) converted from a source hex string
*
* @category Colour
*
* @param alpha Optional Alpha to include if converting 3-part hex colours to rgba
*
* @example
* Returns rgb results from hex colour strings
* ```ts
* const input = ['#000000', '#ffffff', '#00ff00'];
* from(input).pipe(hexToRGBA()).subscribe();
* ```
* Output: `'rgb(0, 0, 0)', 'rgb(255, 255, 255)', 'rgb(0, 255, 0)'`
*
* @example
* Returns rgba results from hex colour strings with opacity
* ```ts
* const input = ['#000000', '#ffffff', '#00ff00'];
* from(input).pipe(hexToRGBA(0.5)).subscribe();
* ```
* Output: `'rgba(0, 0, 0, 0.5)', 'rgba(255, 255, 255, 0.5)', 'rgba(0, 255, 0, 0.5)'`
*
* @example
* Returns rgba results from hex colour strings
* ```ts
* const input = ['000000ff', 'ffffff80', '00ff00e6'];
* from(input).pipe(hexToRGBA()).subscribe();
* ```
* Output: `'rgba(0, 0, 0, 1)', 'rgba(255, 255, 255, 0.5)', 'rgba(0, 255, 0, 0.9)'`
*
* @returns Observable that emits a string containing a HTML hex colour
*/
function hexToRGBA(alpha) {
var alpha$ = createOrReturnObservable(alpha);
return function (source) {
return source.pipe(withLatestFrom(alpha$), map(function (_a) {
var _b = __read(_a, 2),
value = _b[0],
alphaValue = _b[1];
return convertHexToRGBA(value, alphaValue);
}));
};
}
/**
* Returns an Observable that emits the value from either the `trueResult` or `falseResult` based on the result from
* the source with a [[PredicateFn]].
*
* @category Mapping
*
* @remarks Each method can return it's own type which you should handle in later operators
*
* @typeParam I The type of value from the source
* @typeParam T The type returned from the Truthy result
* @typeParam F The type returned from the Falsy result
*
* @param predicate The method to check the value from the source Observable
* @param trueResult The method with return value for a truthy [[PredicateFn]]
* @param falseResult The method with return value for a falsy [[PredicateFn]]
*
* @example
* Returns a FizzBuzz based on the input value
* ```ts
* const input = [ 1, 2, 3, 4, 5, 6, 10, 15, 16 ];
* from(input).pipe(
* mapIf<number, string, number>(
* (value) => value % 15 == 0 || value % 3 == 0 || value % 5 == 0,
* (value) => (value % 15 == 0 ? `FizzBuzz` : value % 3 === 0 ? 'Fizz' : 'Buzz'),
* (value) => value,
* ),
* ).subscribe();
* ```
* Output: `1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 10, 'FizzBuzz', 16`
*
* @returns Observable that emits a value from the truthy or falsy [[MapFn]] based on the [[PredicateFn]] result
*/
function mapIf(predicate, trueResult, falseResult) {
return function (source) {
return source.pipe(map(function (value) {
return predicate(value) ? trueResult(value) : falseResult(value);
}));
};
}
/**
* Returns an Observable that emits a string containing a HTML hex colour converted from a source rgb string
*
* @category Colour
*
* @param excludeHash Optional boolean to exclude the hash (`#`) character from the return result
*
* @example
* Returns hex results from rgb colour strings
* ```ts
* const input = ['rgb(0,0,0)', 'rgb(255,255,255)', 'rgb(0,255,0)'];
* from(input).pipe(rgbToHex()).subscribe();
* ```
* Output: `'#000000', '#ffffff', '#00ff00'`
*
* @example
* Returns hex results from rgb colour strings excluding hash
* ```ts
* const input = ['rgb(0,0,0)', 'rgb(255,255,255)', 'rgb(0,255,0)'];
* from(input).pipe(rgbToHex(true)).subscribe();
* ```
* Output: `'000000', 'ffffff', '00ff00'`
*
* @returns Observable that emits a string containing a HTML hex colour
*/
function rgbToHex(excludeHash) {
var excludeHash$ = createOrReturnObservable(excludeHash);
return function (source) {
return source.pipe(map(function (input) {
return convertRGBAStrToHexComponents(input);
}), withLatestFrom(excludeHash$), map(function (_a) {
var _b = __read(_a, 2),
_c = __read(_b[0], 3),
red = _c[0],
green = _c[1],
blue = _c[2],
excludeHashValue = _b[1];
return excludeHashValue ? "" + red + green + blue : "#" + red + green + blue;
}));
};
}
/**
* Returns an Observable that emits a string containing a HTML hex colour converted from a source rgba string
*
* @category Colour
*
* @param excludeHash Optional boolean to exclude the hash (`#`) character from the return result
*
* @example
* Returns hex results from rgba colour strings
* ```ts
* const input = ['rgba(0,0,0,1)', 'rgba(255,255,255,0.5)', 'rgba(0,255,0,0.9)'];
* from(input).pipe(rgbaToHex()).subscribe();
* ```
* Output: `'#000000ff', '#ffffff80', '#00ff00e6'`
*
* @example
* Returns hex results from rgba colour strings excluding hash
* ```ts
* const input = ['rgba(0,0,0,1)', 'rgba(255,255,255,0.5)', 'rgba(0,255,0,0.9)'];
* from(input).pipe(rgbaToHex(true)).subscribe();
* ```
* Output: `'000000ff', 'ffffff80', '00ff00e6'`
*
* @returns Observable that emits a string containing a HTML hex colour
*/
function rgbaToHex(excludeHash) {
var excludeHash$ = createOrReturnObservable(excludeHash);
return function (source) {
return source.pipe(map(function (input) {
return convertRGBAStrToHexComponents(input);
}), withLatestFrom(excludeHash$), map(function (_a) {
var _b = __read(_a, 2),
hexColour = _b[0],
excludeHashValue = _b[1];
return excludeHashValue ? "" + hexColour.join('') : "#" + hexColour.join('');
}));
};
}
/**
* Returns an Observable that emits the value from either the `trueResult` or `falseResult` based on the result from
* the source with a [[PredicateFn]].
*
* @category Mapping
*
* @remarks Each method can return it's own type which you should handle in later operators
*
* @typeParam I The type of value from the source
* @typeParam T The type returned from the Truthy result
* @typeParam F The type returned from the Falsy result
*
* @param predicate The method to check the value from the source Observable
* @param trueResult The method with return value for a truthy [[PredicateFn]]
* @param falseResult The method with return value for a falsy [[PredicateFn]]
*
* @example
* Returns a FizzBuzz based on the input value
* ```ts
* const input = [ 12, 5, 6, 1, 3, 10 ];
* from(input).pipe(
* switchMapIf<number, boolean>(
* (value) => value <= 6,
* (value) => of(true),
* (value) => of(false),
* ),
* ).subscribe();
* ```
* Output: `true, false, true, false, false, true`
*
* @returns Observable that emits a value based on the [[PredicateFn]] result
*/
function switchMapIf(predicate, trueResult, falseResult) {
return function (source) {
return source.pipe(switchMap(function (value) {
return predicate(value) ? trueResult(value) : falseResult(value);
}));
};
}
/**
* Returns the source Observable that will use the passed AbortSignal to handle unsubscription
*
* @category Subscription
*
* @typeParam T The value of the Observable
*
* @param signal The signal used to end the subscription
*
* @example
* Pass a signal to handle unsubscription
* ```ts
* const stop = new AbortController();
*
* const source$ = from([1, 2, 3, 4, 5]);
*
* source.pipe(takeUntilSignal(stop.signal)).subscribe();
* ```
*
* @returns Observable that will end subscription when the AbortSignal has been fired
*/
function takeUntilSignal(signal) {
var innerSubject$ = new Subject();
signal.onabort = function () {
return innerSubject$.next();
};
return function (source) {
return source.pipe(takeUntil(innerSubject$));
};
}
/**
* Perform a side effect for every emit from the source Observable that passes the [[PredicateFn]], return an Observable
* that is identical to the source.
*
* @category Side Effects
*
* @typeParam T The value type of the source
*
* @param predicate [[PredicateFn]] function to compared the values against
* @param callback [[CallbackFn]] to be executed when this operator is run
*
* @example
* Perform a side effect when the value is `mod2`
* ```ts
* const predicateFn = (value: number) => value % 2 === 0;
* const callbackFn = (value: number) => `${value} is mod2`)
*
* const input = [1, 2, 3, 4, 5, 6];
* from(input).pipe(tapIf(predicateFn, callbackFn).subscribe();
* ```
* Output: `'2 is mod2', '4 is mod2', '6 is mod2'`
*
* @returns Observable that emits the source observable after performing a side effect
*/
function tapIf(predicate, callback) {
return function (source) {
return source.pipe(tap(function (value) {
return predicate(value) && callback(value);
}));
};
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Perform a side effect for the first subscription to the source Observable, return an Observable that is identical to
* the source.
*
* @category Side Effects
*
* @typeParam T The value type of the source
*
* @param callback [[CallbackFn]] to be executed when this operator is run
*
* @example
* Perform a side effect on first subscription to the source
* ```ts
* const input = ['Hello', 'RxJS', 'Ninja'];
* const echoValue = value => `First value is ${value}`;
*
* from(input).pipe(tapOnFirstEmit(echoValue)).subscribe();
* ```
* Output: `Hello`
*
* @returns Observable that emits the source observable after performing a side effect
*/
function tapOnFirstEmit(callback) {
return function (source) {
return of(undefined).pipe(tap(callback), switchMap(function () {
return source;
}));
};
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Perform a side effect for every subscription to the source Observable and return an Observable that is identical to
* the source.
*
* @category Side Effects
*
* @typeParam T The value type of the source
*
* @param callback [[CallbackFn]] to be executed when this operator is run
*
* @example
* Perform a side effect on every new subscription to a source
* ```ts
* const onClick$ = fromEvent(element, 'click').pipe(tapOnSubscribe(( ) => console.log('New Subscription')));
*
* onClick$.subscribe();
* onClick$.subscribe();
* ```
* Output: `'New Subscription', 'New Subscription'`
*
* @returns Observable that emits the source observable after performing a side effect
*/
function tapOnSubscribe(callback) {
return function (source) {
return defer(function () {
return of(undefined).pipe(tap(callback), switchMap(function () {
return source;
}));
});
};
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* Perform a side effect for every unsubscription to the source Observable and return an Observable that is identical to
* the source.
*
* @category Side Effects
*
* @typeParam T The value type of the source
*
* @param callback [[CallbackFn]] to be executed when this operator is run
*
* @example
* Perform a side effect on every new unsubscription to a source
* ```ts
* const onClick$ = fromEvent(element, 'click').pipe(tapOnUnsubscribe(( ) => console.log('End Subscription')));
*
* onClick$.pipe(take(1)).subscribe();
* onClick$.pipe(take(1)).subscribe();
* ```
* Output: `'End Subscription', 'End Subscription'`
*
* @returns Observable that emits the source Observable and calls the `callback` on unsubscription
*/
function tapOnUnsubscribe(callback) {
return function (source) {
return new Observable(function (subscriber) {
var sub = source.subscribe(subscriber);
return function () {
callback();
/* istanbul ignore next */
!sub.closed && sub.unsubscribe();
};
});
};
}
/**
* @packageDocumentation
* @module Utility
*/
/**
* @private
* @internal
*/
var SupportedTemperatures = {
/**
* Celsius
*/
CELSIUS: 'celsius',
/**
* Fahrenheit
*/
FAHRENHEIT: 'fahrenheit',
/**
* Kelvin
*/
KELVIN: 'kelvin',
/**
* Rankine
*/
RANKINE: 'rankine'
};
/**
* Available supported values for conversion with the [[temperature]] operator
*/
var Temperatures;
(function (Temperatures) {
/**
* Celsius
*/
Temperatures["CELSIUS"] = "celsius";
/**
* Fahrenheit
*/
Temperatures["FAHRENHEIT"] = "fahrenheit";
/**
* Kelvin
*/
Temperatures["KELVIN"] = "kelvin";
/**
* Rankine
*/
Temperatures["RANKINE"] = "rankine";
})(Temperatures || (Temperatures = {}));
var _a$1, _b$1, _c$1, _d$1;
/**
* @private
* @internal
*/
var fromCelsius = (_a$1 = {}, _a$1[SupportedTemperatures.CELSIUS] = function (num, precision) {
return roundNumber(num, precision);
}, _a$1[SupportedTemperatures.FAHRENHEIT] = function (num, precision) {
return roundNumber(num * 1.8 + 32, precision);
}, _a$1[SupportedTemperatures.KELVIN] = function (num, precision) {
return roundNumber(num + 273.15, precision);
}, _a$1[SupportedTemperatures.RANKINE] = function (num, precision) {
return roundNumber((num + 273.15) * 1.8, precision);
}, _a$1);
/**
* @private
* @internal
*/
var fromFahrenheit = (_b$1 = {}, _b$1[SupportedTemperat