Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@remote-ui/core

Package Overview
Dependencies
Maintainers
3
Versions
86
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@remote-ui/core - npm Package Compare versions

Comparing version 1.6.0-alpha.0 to 1.6.0

11

build/cjs/receiver.js

@@ -208,8 +208,15 @@ "use strict";

}, {
key: "flush",
value: function flush() {
var _this$timeout;
return (_this$timeout = this.timeout) !== null && _this$timeout !== void 0 ? _this$timeout : Promise.resolve();
}
}, {
key: "enqueueUpdate",
value: function enqueueUpdate(attached) {
var _this$timeout,
var _this$timeout2,
_this3 = this;
this.timeout = (_this$timeout = this.timeout) !== null && _this$timeout !== void 0 ? _this$timeout : new Promise(function (resolve) {
this.timeout = (_this$timeout2 = this.timeout) !== null && _this$timeout2 !== void 0 ? _this$timeout2 : new Promise(function (resolve) {
setTimeout(function () {

@@ -216,0 +223,0 @@ var queuedUpdates = _toConsumableArray(_this3.queuedUpdates);

409

build/cjs/root.js

@@ -60,4 +60,12 @@ "use strict";

};
if (strict) Object.freeze(components);
var remoteRoot = {
kind: _types.KIND_ROOT,
options: strict ? Object.freeze({
strict: strict,
components: components
}) : {
strict: strict,
components: components
},

@@ -78,5 +86,7 @@ get children() {

var initialProps = rest[0],
initialChildren = rest[1];
initialChildren = rest[1],
moreChildren = rest.slice(2);
var normalizedInitialProps = initialProps !== null && initialProps !== void 0 ? initialProps : {};
var normalizedInitialChildren = [];
var normalizedInitialProps = {};
var normalizedInternalProps = {};

@@ -95,3 +105,3 @@ if (initialProps) {

if (_key2 === 'children') continue;
normalizedInitialProps[_key2] = makeValueHotSwappable(initialProps[_key2]);
normalizedInternalProps[_key2] = makeValueHotSwappable(initialProps[_key2]);
}

@@ -101,14 +111,35 @@ }

if (initialChildren) {
var _iterator = _createForOfIteratorHelper(initialChildren),
_step;
if (Array.isArray(initialChildren)) {
var _iterator = _createForOfIteratorHelper(initialChildren),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var child = _step.value;
normalizedInitialChildren.push(normalizeChild(child, remoteRoot));
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var child = _step.value;
normalizedInitialChildren.push(normalizeChild(child, remoteRoot));
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
} else {
normalizedInitialChildren.push(normalizeChild(initialChildren, remoteRoot)); // The complex tuple type of `rest` makes it so `moreChildren` is
// incorrectly inferred as potentially being the props of the component,
// lazy casting since we know it will be an array of child elements
// (or empty).
var _iterator2 = _createForOfIteratorHelper(moreChildren),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _child = _step2.value;
normalizedInitialChildren.push(normalizeChild(_child, remoteRoot));
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}

@@ -119,3 +150,4 @@ }

var internals = {
props: strict ? Object.freeze(normalizedInitialProps) : normalizedInitialProps,
externalProps: strict ? Object.freeze(normalizedInitialProps) : normalizedInitialProps,
internalProps: normalizedInternalProps,
children: strict ? Object.freeze(normalizedInitialChildren) : normalizedInitialChildren

@@ -132,5 +164,9 @@ };

get props() {
return internals.props;
return internals.externalProps;
},
get remoteProps() {
return internals.internalProps;
},
updateProps: function updateProps(newProps) {

@@ -159,14 +195,14 @@ return _updateProps(component, newProps, internals, rootInternals);

var _iterator2 = _createForOfIteratorHelper(internals.children),
_step2;
var _iterator3 = _createForOfIteratorHelper(internals.children),
_step3;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _child = _step2.value;
moveChildToContainer(component, _child, rootInternals);
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var _child2 = _step3.value;
moveChildToContainer(component, _child2, rootInternals);
}
} catch (err) {
_iterator2.e(err);
_iterator3.e(err);
} finally {
_iterator2.f();
_iterator3.f();
}

@@ -209,2 +245,3 @@

mount: function mount() {
if (rootInternals.mounted) return Promise.resolve();
rootInternals.mounted = true;

@@ -227,8 +264,8 @@ return Promise.resolve(channel(_types.ACTION_MOUNT, rootInternals.children.map(serialize)));

if ('children' in element) {
var _iterator3 = _createForOfIteratorHelper(element.children),
_step3;
var _iterator4 = _createForOfIteratorHelper(element.children),
_step4;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var child = _step3.value;
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var child = _step4.value;
withEach(child);

@@ -238,5 +275,5 @@ recurse(child);

} catch (err) {
_iterator3.e(err);
_iterator4.e(err);
} finally {
_iterator3.f();
_iterator4.f();
}

@@ -288,3 +325,3 @@ }

var strict = rootInternals.strict;
var currentProps = internals.props;
var currentProps = internals.internalProps;
var normalizedNewProps = {};

@@ -322,18 +359,19 @@ var hotSwapFunctions = [];

if (hasRemoteChange) {
channel(_types.ACTION_UPDATE_PROPS, component.id, newProps);
channel(_types.ACTION_UPDATE_PROPS, component.id, normalizedNewProps);
}
},
local: function local() {
var mergedProps = _objectSpread(_objectSpread({}, internals.props), newProps);
var mergedExternalProps = _objectSpread(_objectSpread({}, internals.externalProps), newProps);
internals.props = strict ? Object.freeze(mergedProps) : mergedProps;
internals.externalProps = strict ? Object.freeze(mergedExternalProps) : mergedExternalProps;
internals.internalProps = _objectSpread(_objectSpread({}, internals.internalProps), normalizedNewProps);
var _iterator4 = _createForOfIteratorHelper(hotSwapFunctions),
_step4;
var _iterator5 = _createForOfIteratorHelper(hotSwapFunctions),
_step5;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var _step4$value = _slicedToArray(_step4.value, 2),
hotSwappable = _step4$value[0],
_newValue = _step4$value[1];
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
var _step5$value = _slicedToArray(_step5.value, 2),
hotSwappable = _step5$value[0],
_newValue = _step5$value[1];

@@ -343,121 +381,86 @@ hotSwappable[FUNCTION_CURRENT_IMPLEMENTATION_KEY] = _newValue;

} catch (err) {
_iterator4.e(err);
_iterator5.e(err);
} finally {
_iterator4.f();
_iterator5.f();
}
}
});
}
} // Imagine the following remote-ui components we might render in a remote context:
//
// const root = createRemoteRoot();
// const {value, onChange, onPress} = getPropsForValue();
//
// const textField = root.createComponent('TextField', {value, onChange});
// const button = root.createComponent('Button', {onPress});
//
// root.appendChild(textField);
// root.appendChild(button);
//
// function getPropsForValue(value = '') {
// return {
// value,
// onChange: () => {
// const {value, onChange, onPress} = getPropsForValue();
// textField.updateProps({value, onChange});
// button.updateProps({onPress});
// },
// onPress: () => console.log(value),
// };
// }
//
//
// In this example, assume that the `TextField` `onChange` prop is run on blur.
// If this were running on the host, the following steps would happen if you pressed
// on the button:
//
// 1. The text field blurs, and so calls `onChange()` with its current value, which
// then calls `setValue()` with the updated value.
// 2. We synchronously update the `value`, `onChange`, and `onPress` props to point at
// the most current `value`.
// 3. Handling blur is finished, so the browser now handles the click by calling the
// (newly-updated) `Button` `onPress()`, which logs out the new value.
//
// Because remote-ui reproduces a UI tree asynchronously from the remote context, the
// steps above run in a different order:
//
// 1. The text field blurs, and so calls `onChange()` with its current value.
// 2. Handling blur is finished **from the perspective of the main thread**, so the
// browser now handles the click by calling the (original) `Button` `onPress()`, which
// logs out the **initial** value.
// 3. In the remote context, we receive the `onChange()` call, which calls updates the props
// on the `Button` and `TextField` to be based on the new `value`, but by now it’s
// already too late for `onPress` — the old version has already been called!
//
// As you can see, the timing issue introduced by the asynchronous nature of remote-ui
// can cause “old props” to be called from the main thread. This example may seem like
// an unusual pattern, and it is if you are using `@remote-ui/core` directly; you’d generally
// keep a mutable reference to the state, instead of closing over the state with new props.
// However, abstractions on top of `@remote-ui/core`, like the React reconciler in
// `@remote-ui/react`, work almost entirely by closing over state, so this issue is
// much more common with those declarative libraries.
//
// To protect against this, we handle function props a bit differently. When we have a
// function prop, we replace it with a new function that calls the original. However,
// we make the original mutable, by making it a property on the function itself. When
// this function subsequently updates, we don’t send the update to the main thread (as
// we just saw, this can often be "too late" to be of any use). Instead, we swap out
// the mutable reference to the current implementation of the function prop, which can
// be done synchronously. In the example above, this would all happen synchronously in
// the remote context; in our handling of `TextField onChange()`, we update `Button onPress()`,
// and swap out the implementations. Now, when the main thread attempts to call `Button onPress()`,
// it instead calls our wrapper around the function, which can refer to, and call, the
// most recently-applied implementation, instead of directly calling the old implementation.
function tryHotSwappingValues(currentValue, newValue) {
if (typeof currentValue === 'function' && FUNCTION_CURRENT_IMPLEMENTATION_KEY in currentValue) {
return [typeof newValue === 'function' ? IGNORE : makeValueHotSwappable(newValue), [currentValue, newValue]];
return [typeof newValue === 'function' ? IGNORE : makeValueHotSwappable(newValue), [[currentValue, newValue]]];
}
if (Array.isArray(currentValue)) {
if (!Array.isArray(newValue)) {
var _collectNestedHotSwap;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap === void 0 ? void 0 : _collectNestedHotSwap.map(function (hotSwappable) {
return [hotSwappable, undefined];
})];
}
var hasChanged = false;
var hotSwaps = [];
var newLength = newValue.length;
var currentLength = currentValue.length;
var maxLength = Math.max(currentLength, newLength);
var normalizedNewValue = [];
for (var i = 0; i < maxLength; i++) {
var currentElement = currentValue[i];
var newElement = newValue[i];
if (i < newLength) {
if (i >= currentLength) {
hasChanged = true;
normalizedNewValue[i] = makeValueHotSwappable(newValue);
} else {
var _tryHotSwappingValues3 = tryHotSwappingValues(currentElement, newElement),
_tryHotSwappingValues4 = _slicedToArray(_tryHotSwappingValues3, 2),
updatedValue = _tryHotSwappingValues4[0],
elementHotSwaps = _tryHotSwappingValues4[1];
if (elementHotSwaps) hotSwaps.push.apply(hotSwaps, _toConsumableArray(elementHotSwaps));
if (updatedValue === IGNORE) {
normalizedNewValue[i] = currentValue;
} else {
hasChanged = true;
normalizedNewValue[i] = updatedValue;
}
}
} else {
hasChanged = true;
var nestedHotSwappables = collectNestedHotSwappableValues(currentElement);
if (nestedHotSwappables) {
hotSwaps.push.apply(hotSwaps, _toConsumableArray(nestedHotSwappables.map(function (hotSwappable) {
return [hotSwappable, undefined];
})));
}
}
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
return tryHotSwappingArrayValues(currentValue, newValue);
}
if (_typeof(currentValue) === 'object' && currentValue != null) {
if (_typeof(newValue) !== 'object' || newValue == null) {
var _collectNestedHotSwap2;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap2 = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap2 === void 0 ? void 0 : _collectNestedHotSwap2.map(function (hotSwappable) {
return [hotSwappable, undefined];
})];
}
var _hasChanged = false;
var _hotSwaps = [];
var _normalizedNewValue = {}; // eslint-disable-next-line guard-for-in
for (var _key4 in currentValue) {
var _currentElement = currentValue[_key4];
if (!(_key4 in newValue)) {
_hasChanged = true;
var _nestedHotSwappables = collectNestedHotSwappableValues(_currentElement);
if (_nestedHotSwappables) {
_hotSwaps.push.apply(_hotSwaps, _toConsumableArray(_nestedHotSwappables.map(function (hotSwappable) {
return [hotSwappable, undefined];
})));
}
}
var _newElement = newValue[_key4];
var _tryHotSwappingValues5 = tryHotSwappingValues(_currentElement, _newElement),
_tryHotSwappingValues6 = _slicedToArray(_tryHotSwappingValues5, 2),
_updatedValue = _tryHotSwappingValues6[0],
_elementHotSwaps = _tryHotSwappingValues6[1];
if (_elementHotSwaps) _hotSwaps.push.apply(_hotSwaps, _toConsumableArray(_elementHotSwaps));
if (_updatedValue === IGNORE) {
_normalizedNewValue[_key4] = currentValue;
} else {
_hasChanged = true;
_normalizedNewValue[_key4] = _updatedValue;
}
}
for (var _key5 in newValue) {
if (_key5 in _normalizedNewValue) continue;
_hasChanged = true;
_normalizedNewValue[_key5] = makeValueHotSwappable(newValue[_key5]);
}
return [_hasChanged ? _normalizedNewValue : IGNORE, _hotSwaps];
return tryHotSwappingObjectValues(currentValue, newValue);
}

@@ -481,2 +484,9 @@

return wrappedFunction;
} else if (Array.isArray(value)) {
return value.map(makeValueHotSwappable);
} else if (_typeof(value) === 'object' && value != null) {
return Object.keys(value).reduce(function (newValue, key) {
newValue[key] = makeValueHotSwappable(value[key]);
return newValue;
}, {});
}

@@ -619,7 +629,9 @@

id: value.id,
kind: value.kind,
text: value.text
} : {
id: value.id,
kind: value.kind,
type: value.type,
props: value.props,
props: value.remoteProps,
children: value.children.map(function (child) {

@@ -644,2 +656,109 @@ return serialize(child);

});
}
function tryHotSwappingObjectValues(currentValue, newValue) {
if (_typeof(newValue) !== 'object' || newValue == null) {
var _collectNestedHotSwap;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap === void 0 ? void 0 : _collectNestedHotSwap.map(function (hotSwappable) {
return [hotSwappable, undefined];
})];
}
var hasChanged = false;
var hotSwaps = [];
var normalizedNewValue = {}; // eslint-disable-next-line guard-for-in
for (var _key4 in currentValue) {
var currentObjectValue = currentValue[_key4];
if (!(_key4 in newValue)) {
hasChanged = true;
var nestedHotSwappables = collectNestedHotSwappableValues(currentObjectValue);
if (nestedHotSwappables) {
hotSwaps.push.apply(hotSwaps, _toConsumableArray(nestedHotSwappables.map(function (hotSwappable) {
return [hotSwappable, undefined];
})));
}
}
var newObjectValue = newValue[_key4];
var _tryHotSwappingValues3 = tryHotSwappingValues(currentObjectValue, newObjectValue),
_tryHotSwappingValues4 = _slicedToArray(_tryHotSwappingValues3, 2),
updatedValue = _tryHotSwappingValues4[0],
elementHotSwaps = _tryHotSwappingValues4[1];
if (elementHotSwaps) hotSwaps.push.apply(hotSwaps, _toConsumableArray(elementHotSwaps));
if (updatedValue !== IGNORE) {
hasChanged = true;
normalizedNewValue[_key4] = updatedValue;
}
}
for (var _key5 in newValue) {
if (_key5 in normalizedNewValue) continue;
hasChanged = true;
normalizedNewValue[_key5] = makeValueHotSwappable(newValue[_key5]);
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
}
function tryHotSwappingArrayValues(currentValue, newValue) {
if (!Array.isArray(newValue)) {
var _collectNestedHotSwap2;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap2 = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap2 === void 0 ? void 0 : _collectNestedHotSwap2.map(function (hotSwappable) {
return [hotSwappable, undefined];
})];
}
var hasChanged = false;
var hotSwaps = [];
var newLength = newValue.length;
var currentLength = currentValue.length;
var maxLength = Math.max(currentLength, newLength);
var normalizedNewValue = [];
for (var i = 0; i < maxLength; i++) {
var currentArrayValue = currentValue[i];
var newArrayValue = newValue[i];
if (i < newLength) {
if (i >= currentLength) {
hasChanged = true;
normalizedNewValue[i] = makeValueHotSwappable(newArrayValue);
continue;
}
var _tryHotSwappingValues5 = tryHotSwappingValues(currentArrayValue, newArrayValue),
_tryHotSwappingValues6 = _slicedToArray(_tryHotSwappingValues5, 2),
updatedValue = _tryHotSwappingValues6[0],
elementHotSwaps = _tryHotSwappingValues6[1];
if (elementHotSwaps) hotSwaps.push.apply(hotSwaps, _toConsumableArray(elementHotSwaps));
if (updatedValue === IGNORE) {
normalizedNewValue[i] = currentArrayValue;
continue;
}
hasChanged = true;
normalizedNewValue[i] = updatedValue;
} else {
hasChanged = true;
var nestedHotSwappables = collectNestedHotSwappableValues(currentArrayValue);
if (nestedHotSwappables) {
hotSwaps.push.apply(hotSwaps, _toConsumableArray(nestedHotSwappables.map(function (hotSwappable) {
return [hotSwappable, undefined];
})));
}
}
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
}

@@ -5,4 +5,28 @@ "use strict";

var _receiver = require("../receiver");
function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e2) { throw _e2; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e3) { didErr = true; err = _e3; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
describe('root', function () {
describe('createComponent', function () {
describe('createComponent()', function () {
it('does not throw error when no allowed components are set', function () {

@@ -42,3 +66,3 @@ var root = (0, _root.createRemoteRoot)(function () {});

});
describe('createText', function () {
describe('createText()', function () {
it('does not throw error when appending a child created by the remote root', function () {

@@ -54,3 +78,3 @@ var components = [];

});
describe('appendChild', function () {
describe('appendChild()', function () {
it('does not throw error when appending a child created by the remote root', function () {

@@ -71,3 +95,3 @@ var root = (0, _root.createRemoteRoot)(function () {});

});
describe('insertChildBefore', function () {
describe('insertChildBefore()', function () {
it('does not throw error when calling insertChildBefore for a component created by the remote root', function () {

@@ -92,2 +116,185 @@ var root = (0, _root.createRemoteRoot)(function () {});

});
});
describe('hot-swapping', function () {
it('hot-swaps function props', function () {
var funcOne = jest.fn();
var funcTwo = jest.fn();
var receiver = createDelayedReceiver();
var root = (0, _root.createRemoteRoot)(receiver.receive);
var button = root.createComponent('Button', {
onPress: funcOne
});
root.appendChild(button);
root.mount(); // After this, the receiver will have the initial Button component
receiver.flush();
button.updateProps({
onPress: funcTwo
});
receiver.children[0].props.onPress();
expect(funcOne).not.toHaveBeenCalled();
expect(funcTwo).toHaveBeenCalled();
});
it('hot-swaps function props nested in objects', function () {
var funcOne = jest.fn();
var funcTwo = jest.fn();
var receiver = createDelayedReceiver();
var root = (0, _root.createRemoteRoot)(receiver.receive);
var resourceList = root.createComponent('ResourceList', {
filterControl: {
onQueryChange: funcOne,
queryValue: 'foo'
}
});
root.appendChild(resourceList);
root.mount();
resourceList.updateProps({
filterControl: {
onQueryChange: funcTwo,
queryValue: 'bar'
}
}); // After this, the receiver will have the initial ResourceList component
receiver.flush();
var receivedResourceList = receiver.children[0];
var queryValue = receivedResourceList.props.filterControl.queryValue;
receivedResourceList.props.filterControl.onQueryChange();
expect(queryValue).toStrictEqual('bar');
expect(funcOne).not.toHaveBeenCalled();
expect(funcTwo).toHaveBeenCalled();
});
it('hot-swaps function props nested in objects and arrays', function () {
var funcOne = jest.fn();
var funcTwo = jest.fn();
var receiver = createDelayedReceiver();
var root = (0, _root.createRemoteRoot)(receiver.receive);
var card = root.createComponent('Card', {
actions: [{
onAction: funcOne
}]
});
root.appendChild(card);
root.mount(); // After this, the receiver will have the initial Card component
receiver.flush();
card.updateProps({
actions: [{
onAction: funcTwo
}]
});
receiver.children[0].props.actions[0].onAction();
expect(funcOne).not.toHaveBeenCalled();
expect(funcTwo).toHaveBeenCalled();
});
it('hot-swaps function props for arrays when the length increases', function () {
var firstActionFuncOne = jest.fn();
var firstActionFuncTwo = jest.fn();
var secondActionFunc = jest.fn();
var receiver = createDelayedReceiver();
var root = (0, _root.createRemoteRoot)(receiver.receive);
var modal = root.createComponent('Modal', {
secondaryActions: [{
onAction: firstActionFuncOne
}]
});
root.appendChild(modal);
root.mount();
modal.updateProps({
secondaryActions: [{
onAction: firstActionFuncTwo
}, {
onAction: secondActionFunc
}]
});
receiver.flush();
var _props$secondaryActio = _slicedToArray(receiver.children[0].props.secondaryActions, 2),
firstAction = _props$secondaryActio[0],
secondAction = _props$secondaryActio[1];
firstAction.onAction();
secondAction.onAction();
expect(firstActionFuncOne).not.toHaveBeenCalled();
expect(firstActionFuncTwo).toHaveBeenCalled();
expect(secondActionFunc).toHaveBeenCalled();
});
it('hot-swaps function props for nested arrays', function () {
var firstActionFuncOne = jest.fn();
var firstActionFuncTwo = jest.fn();
var secondActionFuncOne = jest.fn();
var receiver = createDelayedReceiver();
var root = (0, _root.createRemoteRoot)(receiver.receive);
var modal = root.createComponent('Modal', {
actionGroups: [{
actions: [{
onAction: firstActionFuncOne
}]
}]
});
root.appendChild(modal);
root.mount();
modal.updateProps({
actionGroups: [{
actions: [{
onAction: firstActionFuncTwo
}, {
onAction: secondActionFuncOne
}]
}]
});
receiver.flush();
var _props$actionGroups = _slicedToArray(receiver.children[0].props.actionGroups, 1),
_props$actionGroups$ = _slicedToArray(_props$actionGroups[0].actions, 2),
actionOne = _props$actionGroups$[0],
actionTwo = _props$actionGroups$[1];
actionOne.onAction();
actionTwo.onAction();
expect(firstActionFuncOne).not.toHaveBeenCalled();
expect(firstActionFuncTwo).toHaveBeenCalled();
expect(secondActionFuncOne).toHaveBeenCalled();
});
});
});
function createDelayedReceiver() {
var receiver = new _receiver.RemoteReceiver();
var enqueued = new Set();
return {
get children() {
return receiver.root.children;
},
receive: function (type) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var perform = function perform() {
receiver.receive.apply(receiver, [type].concat(args));
enqueued["delete"](perform);
};
enqueued.add(perform);
},
flush: function flush() {
var currentlyEnqueued = _toConsumableArray(enqueued);
enqueued.clear();
var _iterator = _createForOfIteratorHelper(currentlyEnqueued),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var perform = _step.value;
perform();
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
};
}

@@ -12,7 +12,7 @@ "use strict";

function isRemoteComponent(child) {
return child.kind === _types.KIND_COMPONENT;
return child != null && child.kind === _types.KIND_COMPONENT;
}
function isRemoteText(child) {
return child.kind === _types.KIND_TEXT;
return child != null && child.kind === _types.KIND_TEXT;
}

@@ -141,6 +141,12 @@ "use strict";

enqueueUpdate(attached) {
flush() {
var _this$timeout;
this.timeout = (_this$timeout = this.timeout) !== null && _this$timeout !== void 0 ? _this$timeout : new Promise(resolve => {
return (_this$timeout = this.timeout) !== null && _this$timeout !== void 0 ? _this$timeout : Promise.resolve();
}
enqueueUpdate(attached) {
var _this$timeout2;
this.timeout = (_this$timeout2 = this.timeout) !== null && _this$timeout2 !== void 0 ? _this$timeout2 : new Promise(resolve => {
setTimeout(() => {

@@ -147,0 +153,0 @@ const queuedUpdates = [...this.queuedUpdates];

@@ -28,4 +28,12 @@ "use strict";

};
if (strict) Object.freeze(components);
const remoteRoot = {
kind: _types.KIND_ROOT,
options: strict ? Object.freeze({
strict,
components
}) : {
strict,
components
},

@@ -41,5 +49,6 @@ get children() {

const [initialProps, initialChildren] = rest;
const [initialProps, initialChildren, ...moreChildren] = rest;
const normalizedInitialProps = initialProps !== null && initialProps !== void 0 ? initialProps : {};
const normalizedInitialChildren = [];
const normalizedInitialProps = {};
const normalizedInternalProps = {};

@@ -57,3 +66,3 @@ if (initialProps) {

if (key === 'children') continue;
normalizedInitialProps[key] = makeValueHotSwappable(initialProps[key]);
normalizedInternalProps[key] = makeValueHotSwappable(initialProps[key]);
}

@@ -63,4 +72,15 @@ }

if (initialChildren) {
for (const child of initialChildren) {
normalizedInitialChildren.push(normalizeChild(child, remoteRoot));
if (Array.isArray(initialChildren)) {
for (const child of initialChildren) {
normalizedInitialChildren.push(normalizeChild(child, remoteRoot));
}
} else {
normalizedInitialChildren.push(normalizeChild(initialChildren, remoteRoot)); // The complex tuple type of `rest` makes it so `moreChildren` is
// incorrectly inferred as potentially being the props of the component,
// lazy casting since we know it will be an array of child elements
// (or empty).
for (const child of moreChildren) {
normalizedInitialChildren.push(normalizeChild(child, remoteRoot));
}
}

@@ -71,3 +91,4 @@ }

const internals = {
props: strict ? Object.freeze(normalizedInitialProps) : normalizedInitialProps,
externalProps: strict ? Object.freeze(normalizedInitialProps) : normalizedInitialProps,
internalProps: normalizedInternalProps,
children: strict ? Object.freeze(normalizedInitialChildren) : normalizedInitialChildren

@@ -83,5 +104,9 @@ };

get props() {
return internals.props;
return internals.externalProps;
},
get remoteProps() {
return internals.internalProps;
},
updateProps: newProps => updateProps(component, newProps, internals, rootInternals),

@@ -140,2 +165,3 @@ appendChild: child => appendChild(component, normalizeChild(child, remoteRoot), internals, rootInternals),

mount() {
if (rootInternals.mounted) return Promise.resolve();
rootInternals.mounted = true;

@@ -213,3 +239,3 @@ return Promise.resolve(channel(_types.ACTION_MOUNT, rootInternals.children.map(serialize)));

const {
props: currentProps
internalProps: currentProps
} = internals;

@@ -244,10 +270,13 @@ const normalizedNewProps = {};

if (hasRemoteChange) {
channel(_types.ACTION_UPDATE_PROPS, component.id, newProps);
channel(_types.ACTION_UPDATE_PROPS, component.id, normalizedNewProps);
}
},
local: () => {
const mergedProps = { ...internals.props,
const mergedExternalProps = { ...internals.externalProps,
...newProps
};
internals.props = strict ? Object.freeze(mergedProps) : mergedProps;
internals.externalProps = strict ? Object.freeze(mergedExternalProps) : mergedExternalProps;
internals.internalProps = { ...internals.internalProps,
...normalizedNewProps
};

@@ -259,97 +288,80 @@ for (const [hotSwappable, newValue] of hotSwapFunctions) {

});
}
} // Imagine the following remote-ui components we might render in a remote context:
//
// const root = createRemoteRoot();
// const {value, onChange, onPress} = getPropsForValue();
//
// const textField = root.createComponent('TextField', {value, onChange});
// const button = root.createComponent('Button', {onPress});
//
// root.appendChild(textField);
// root.appendChild(button);
//
// function getPropsForValue(value = '') {
// return {
// value,
// onChange: () => {
// const {value, onChange, onPress} = getPropsForValue();
// textField.updateProps({value, onChange});
// button.updateProps({onPress});
// },
// onPress: () => console.log(value),
// };
// }
//
//
// In this example, assume that the `TextField` `onChange` prop is run on blur.
// If this were running on the host, the following steps would happen if you pressed
// on the button:
//
// 1. The text field blurs, and so calls `onChange()` with its current value, which
// then calls `setValue()` with the updated value.
// 2. We synchronously update the `value`, `onChange`, and `onPress` props to point at
// the most current `value`.
// 3. Handling blur is finished, so the browser now handles the click by calling the
// (newly-updated) `Button` `onPress()`, which logs out the new value.
//
// Because remote-ui reproduces a UI tree asynchronously from the remote context, the
// steps above run in a different order:
//
// 1. The text field blurs, and so calls `onChange()` with its current value.
// 2. Handling blur is finished **from the perspective of the main thread**, so the
// browser now handles the click by calling the (original) `Button` `onPress()`, which
// logs out the **initial** value.
// 3. In the remote context, we receive the `onChange()` call, which calls updates the props
// on the `Button` and `TextField` to be based on the new `value`, but by now it’s
// already too late for `onPress` — the old version has already been called!
//
// As you can see, the timing issue introduced by the asynchronous nature of remote-ui
// can cause “old props” to be called from the main thread. This example may seem like
// an unusual pattern, and it is if you are using `@remote-ui/core` directly; you’d generally
// keep a mutable reference to the state, instead of closing over the state with new props.
// However, abstractions on top of `@remote-ui/core`, like the React reconciler in
// `@remote-ui/react`, work almost entirely by closing over state, so this issue is
// much more common with those declarative libraries.
//
// To protect against this, we handle function props a bit differently. When we have a
// function prop, we replace it with a new function that calls the original. However,
// we make the original mutable, by making it a property on the function itself. When
// this function subsequently updates, we don’t send the update to the main thread (as
// we just saw, this can often be "too late" to be of any use). Instead, we swap out
// the mutable reference to the current implementation of the function prop, which can
// be done synchronously. In the example above, this would all happen synchronously in
// the remote context; in our handling of `TextField onChange()`, we update `Button onPress()`,
// and swap out the implementations. Now, when the main thread attempts to call `Button onPress()`,
// it instead calls our wrapper around the function, which can refer to, and call, the
// most recently-applied implementation, instead of directly calling the old implementation.
function tryHotSwappingValues(currentValue, newValue) {
if (typeof currentValue === 'function' && FUNCTION_CURRENT_IMPLEMENTATION_KEY in currentValue) {
return [typeof newValue === 'function' ? IGNORE : makeValueHotSwappable(newValue), [currentValue, newValue]];
return [typeof newValue === 'function' ? IGNORE : makeValueHotSwappable(newValue), [[currentValue, newValue]]];
}
if (Array.isArray(currentValue)) {
if (!Array.isArray(newValue)) {
var _collectNestedHotSwap;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap === void 0 ? void 0 : _collectNestedHotSwap.map(hotSwappable => [hotSwappable, undefined])];
}
let hasChanged = false;
const hotSwaps = [];
const newLength = newValue.length;
const currentLength = currentValue.length;
const maxLength = Math.max(currentLength, newLength);
const normalizedNewValue = [];
for (let i = 0; i < maxLength; i++) {
const currentElement = currentValue[i];
const newElement = newValue[i];
if (i < newLength) {
if (i >= currentLength) {
hasChanged = true;
normalizedNewValue[i] = makeValueHotSwappable(newValue);
} else {
const [updatedValue, elementHotSwaps] = tryHotSwappingValues(currentElement, newElement);
if (elementHotSwaps) hotSwaps.push(...elementHotSwaps);
if (updatedValue === IGNORE) {
normalizedNewValue[i] = currentValue;
} else {
hasChanged = true;
normalizedNewValue[i] = updatedValue;
}
}
} else {
hasChanged = true;
const nestedHotSwappables = collectNestedHotSwappableValues(currentElement);
if (nestedHotSwappables) {
hotSwaps.push(...nestedHotSwappables.map(hotSwappable => [hotSwappable, undefined]));
}
}
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
return tryHotSwappingArrayValues(currentValue, newValue);
}
if (typeof currentValue === 'object' && currentValue != null) {
if (typeof newValue !== 'object' || newValue == null) {
var _collectNestedHotSwap2;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap2 = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap2 === void 0 ? void 0 : _collectNestedHotSwap2.map(hotSwappable => [hotSwappable, undefined])];
}
let hasChanged = false;
const hotSwaps = [];
const normalizedNewValue = {}; // eslint-disable-next-line guard-for-in
for (const key in currentValue) {
const currentElement = currentValue[key];
if (!(key in newValue)) {
hasChanged = true;
const nestedHotSwappables = collectNestedHotSwappableValues(currentElement);
if (nestedHotSwappables) {
hotSwaps.push(...nestedHotSwappables.map(hotSwappable => [hotSwappable, undefined]));
}
}
const newElement = newValue[key];
const [updatedValue, elementHotSwaps] = tryHotSwappingValues(currentElement, newElement);
if (elementHotSwaps) hotSwaps.push(...elementHotSwaps);
if (updatedValue === IGNORE) {
normalizedNewValue[key] = currentValue;
} else {
hasChanged = true;
normalizedNewValue[key] = updatedValue;
}
}
for (const key in newValue) {
if (key in normalizedNewValue) continue;
hasChanged = true;
normalizedNewValue[key] = makeValueHotSwappable(newValue[key]);
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
return tryHotSwappingObjectValues(currentValue, newValue);
}

@@ -373,2 +385,9 @@

return wrappedFunction;
} else if (Array.isArray(value)) {
return value.map(makeValueHotSwappable);
} else if (typeof value === 'object' && value != null) {
return Object.keys(value).reduce((newValue, key) => {
newValue[key] = makeValueHotSwappable(value[key]);
return newValue;
}, {});
}

@@ -508,7 +527,9 @@

id: value.id,
kind: value.kind,
text: value.text
} : {
id: value.id,
kind: value.kind,
type: value.type,
props: value.props,
props: value.remoteProps,
children: value.children.map(child => serialize(child))

@@ -531,2 +552,92 @@ };

});
}
function tryHotSwappingObjectValues(currentValue, newValue) {
if (typeof newValue !== 'object' || newValue == null) {
var _collectNestedHotSwap;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap === void 0 ? void 0 : _collectNestedHotSwap.map(hotSwappable => [hotSwappable, undefined])];
}
let hasChanged = false;
const hotSwaps = [];
const normalizedNewValue = {}; // eslint-disable-next-line guard-for-in
for (const key in currentValue) {
const currentObjectValue = currentValue[key];
if (!(key in newValue)) {
hasChanged = true;
const nestedHotSwappables = collectNestedHotSwappableValues(currentObjectValue);
if (nestedHotSwappables) {
hotSwaps.push(...nestedHotSwappables.map(hotSwappable => [hotSwappable, undefined]));
}
}
const newObjectValue = newValue[key];
const [updatedValue, elementHotSwaps] = tryHotSwappingValues(currentObjectValue, newObjectValue);
if (elementHotSwaps) hotSwaps.push(...elementHotSwaps);
if (updatedValue !== IGNORE) {
hasChanged = true;
normalizedNewValue[key] = updatedValue;
}
}
for (const key in newValue) {
if (key in normalizedNewValue) continue;
hasChanged = true;
normalizedNewValue[key] = makeValueHotSwappable(newValue[key]);
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
}
function tryHotSwappingArrayValues(currentValue, newValue) {
if (!Array.isArray(newValue)) {
var _collectNestedHotSwap2;
return [makeValueHotSwappable(newValue), (_collectNestedHotSwap2 = collectNestedHotSwappableValues(currentValue)) === null || _collectNestedHotSwap2 === void 0 ? void 0 : _collectNestedHotSwap2.map(hotSwappable => [hotSwappable, undefined])];
}
let hasChanged = false;
const hotSwaps = [];
const newLength = newValue.length;
const currentLength = currentValue.length;
const maxLength = Math.max(currentLength, newLength);
const normalizedNewValue = [];
for (let i = 0; i < maxLength; i++) {
const currentArrayValue = currentValue[i];
const newArrayValue = newValue[i];
if (i < newLength) {
if (i >= currentLength) {
hasChanged = true;
normalizedNewValue[i] = makeValueHotSwappable(newArrayValue);
continue;
}
const [updatedValue, elementHotSwaps] = tryHotSwappingValues(currentArrayValue, newArrayValue);
if (elementHotSwaps) hotSwaps.push(...elementHotSwaps);
if (updatedValue === IGNORE) {
normalizedNewValue[i] = currentArrayValue;
continue;
}
hasChanged = true;
normalizedNewValue[i] = updatedValue;
} else {
hasChanged = true;
const nestedHotSwappables = collectNestedHotSwappableValues(currentArrayValue);
if (nestedHotSwappables) {
hotSwaps.push(...nestedHotSwappables.map(hotSwappable => [hotSwappable, undefined]));
}
}
}
return [hasChanged ? normalizedNewValue : IGNORE, hotSwaps];
}

@@ -5,4 +5,6 @@ "use strict";

var _receiver = require("../receiver");
describe('root', () => {
describe('createComponent', () => {
describe('createComponent()', () => {
it('does not throw error when no allowed components are set', () => {

@@ -42,3 +44,3 @@ const root = (0, _root.createRemoteRoot)(() => {});

});
describe('createText', () => {
describe('createText()', () => {
it('does not throw error when appending a child created by the remote root', () => {

@@ -54,3 +56,3 @@ const components = [];

});
describe('appendChild', () => {
describe('appendChild()', () => {
it('does not throw error when appending a child created by the remote root', () => {

@@ -71,3 +73,3 @@ const root = (0, _root.createRemoteRoot)(() => {});

});
describe('insertChildBefore', () => {
describe('insertChildBefore()', () => {
it('does not throw error when calling insertChildBefore for a component created by the remote root', () => {

@@ -92,2 +94,169 @@ const root = (0, _root.createRemoteRoot)(() => {});

});
});
describe('hot-swapping', () => {
it('hot-swaps function props', () => {
const funcOne = jest.fn();
const funcTwo = jest.fn();
const receiver = createDelayedReceiver();
const root = (0, _root.createRemoteRoot)(receiver.receive);
const button = root.createComponent('Button', {
onPress: funcOne
});
root.appendChild(button);
root.mount(); // After this, the receiver will have the initial Button component
receiver.flush();
button.updateProps({
onPress: funcTwo
});
receiver.children[0].props.onPress();
expect(funcOne).not.toHaveBeenCalled();
expect(funcTwo).toHaveBeenCalled();
});
it('hot-swaps function props nested in objects', () => {
const funcOne = jest.fn();
const funcTwo = jest.fn();
const receiver = createDelayedReceiver();
const root = (0, _root.createRemoteRoot)(receiver.receive);
const resourceList = root.createComponent('ResourceList', {
filterControl: {
onQueryChange: funcOne,
queryValue: 'foo'
}
});
root.appendChild(resourceList);
root.mount();
resourceList.updateProps({
filterControl: {
onQueryChange: funcTwo,
queryValue: 'bar'
}
}); // After this, the receiver will have the initial ResourceList component
receiver.flush();
const receivedResourceList = receiver.children[0];
const queryValue = receivedResourceList.props.filterControl.queryValue;
receivedResourceList.props.filterControl.onQueryChange();
expect(queryValue).toStrictEqual('bar');
expect(funcOne).not.toHaveBeenCalled();
expect(funcTwo).toHaveBeenCalled();
});
it('hot-swaps function props nested in objects and arrays', () => {
const funcOne = jest.fn();
const funcTwo = jest.fn();
const receiver = createDelayedReceiver();
const root = (0, _root.createRemoteRoot)(receiver.receive);
const card = root.createComponent('Card', {
actions: [{
onAction: funcOne
}]
});
root.appendChild(card);
root.mount(); // After this, the receiver will have the initial Card component
receiver.flush();
card.updateProps({
actions: [{
onAction: funcTwo
}]
});
receiver.children[0].props.actions[0].onAction();
expect(funcOne).not.toHaveBeenCalled();
expect(funcTwo).toHaveBeenCalled();
});
it('hot-swaps function props for arrays when the length increases', () => {
const firstActionFuncOne = jest.fn();
const firstActionFuncTwo = jest.fn();
const secondActionFunc = jest.fn();
const receiver = createDelayedReceiver();
const root = (0, _root.createRemoteRoot)(receiver.receive);
const modal = root.createComponent('Modal', {
secondaryActions: [{
onAction: firstActionFuncOne
}]
});
root.appendChild(modal);
root.mount();
modal.updateProps({
secondaryActions: [{
onAction: firstActionFuncTwo
}, {
onAction: secondActionFunc
}]
});
receiver.flush();
const {
secondaryActions: [firstAction, secondAction]
} = receiver.children[0].props;
firstAction.onAction();
secondAction.onAction();
expect(firstActionFuncOne).not.toHaveBeenCalled();
expect(firstActionFuncTwo).toHaveBeenCalled();
expect(secondActionFunc).toHaveBeenCalled();
});
it('hot-swaps function props for nested arrays', () => {
const firstActionFuncOne = jest.fn();
const firstActionFuncTwo = jest.fn();
const secondActionFuncOne = jest.fn();
const receiver = createDelayedReceiver();
const root = (0, _root.createRemoteRoot)(receiver.receive);
const modal = root.createComponent('Modal', {
actionGroups: [{
actions: [{
onAction: firstActionFuncOne
}]
}]
});
root.appendChild(modal);
root.mount();
modal.updateProps({
actionGroups: [{
actions: [{
onAction: firstActionFuncTwo
}, {
onAction: secondActionFuncOne
}]
}]
});
receiver.flush();
const {
actionGroups: [{
actions: [actionOne, actionTwo]
}]
} = receiver.children[0].props;
actionOne.onAction();
actionTwo.onAction();
expect(firstActionFuncOne).not.toHaveBeenCalled();
expect(firstActionFuncTwo).toHaveBeenCalled();
expect(secondActionFuncOne).toHaveBeenCalled();
});
});
});
function createDelayedReceiver() {
const receiver = new _receiver.RemoteReceiver();
const enqueued = new Set();
return {
get children() {
return receiver.root.children;
},
receive: (type, ...args) => {
const perform = () => {
receiver.receive(type, ...args);
enqueued.delete(perform);
};
enqueued.add(perform);
},
flush() {
const currentlyEnqueued = [...enqueued];
enqueued.clear();
for (const perform of currentlyEnqueued) {
perform();
}
}
};
}

@@ -12,7 +12,7 @@ "use strict";

function isRemoteComponent(child) {
return child.kind === _types.KIND_COMPONENT;
return child != null && child.kind === _types.KIND_COMPONENT;
}
function isRemoteText(child) {
return child.kind === _types.KIND_TEXT;
return child != null && child.kind === _types.KIND_TEXT;
}

@@ -10,2 +10,8 @@ # Changelog

## [1.6.0] - 2020-12-04
- `RemoteReceiver` now has a `flush` method that returns a promise for a time after all in-progress updates are finished ([pull request](https://github.com/Shopify/remote-ui/pull/47)).
- `RemoteRoot` now has an `options` field that allows a user of the root to determine whether it was constructed in strict mode, and what components are available for rendering ([pull request](https://github.com/Shopify/remote-ui/pull/47)).
- The serialization of components and text now includes a `kind` key that uses the same `KIND_REMOTE_TEXT` or `KIND_REMOTE_COMPONENT` constants as the original object ([pull request](https://github.com/Shopify/remote-ui/pull/47)).
## [1.5.0] - 2020-10-26

@@ -12,0 +18,0 @@

{
"name": "@remote-ui/core",
"version": "1.6.0-alpha.0",
"version": "1.6.0",
"publishConfig": {

@@ -23,6 +23,6 @@ "access": "public",

"dependencies": {
"@remote-ui/rpc": "^1.1.0-alpha.0",
"@remote-ui/rpc": "^1.0.12",
"@remote-ui/types": "^1.0.4"
},
"gitHead": "95b058fdbc84958f3e10bb4dfd8029a6f0189078"
"gitHead": "8a785421d316205986b47a04b2d25454ad90892f"
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc