Skip to content

3.0.0 Release Candidate

Pre-release
Pre-release
Compare
Choose a tag to compare
@bajtos bajtos released this 05 Oct 13:58
· 185 commits to master since this release

This is the first Release Candidate release of the upcoming major version 3.0.

List of notable changes made between 2.x and 3.0 in this repository follows below.

Invocation for remote method has been changed.

We are deprecating the SharedClass.prototype.find and
SharedClass.prototype.disableMethod for looking up and disabling remote
methods in 3.0. They will be replaced by SharedClass.prototype.findMethodByName
and SharedClass.prototype.disableMethodByName where you can pass in a static
method or a prototype method name. These new methods will accept a string in the
form of "name" for a static method and "prototype.name" for a prototype method.

To find a static remote method:
findMethodByName('create')

To find a prototype remote method:
findMethodByName('prototype.updateAttributes')

To disable a static remote method
disableMethodByName('create')

To disable a prototype remote method:
disableMethodByName('prototype.updateAttributes')

New error-handler for rest-adapter

The REST adapter (typically created via loopback.rest()) uses a new error
handler implementation that provides more secure configuration out of the box.

The error.status has been removed and will be replaced by error.statusCode,
statusCode is more descriptive and avoids ambiguity, users relying on status
will need to change implementation accordingly.

To replicate old behavior user can specify config.local.js as follow:

module.exports = {
  remoting : {
    errorHandler: {
      handler : function(err, req, res, defaultHandler) {
        err.status = err.statusCode;
        defaultHandler();
      }
    }
  }
}

The environment setting NODE_ENV='production' is no longer supported,
production vs. debug mode is controlled exclusively by configuration.
Production environment is assumed by default, the insecure debug mode
must be explicitely turned on.

You can learn more about the rationale behind the new handler in
this comment

User can specific options in their applications in config.json as follow:

{
  "restApiRoot": "/api",
  "host": "0.0.0.0",
  "port": 3000,
  "remoting": {
    "errorHandler": {
      "debug": false,
      "log": false
    }
  }
}

Production mode

Stack trace is never returned in HTTP responses.

Bad Request errors (4xx) provide the following properties copied from the
error object: name, message, statusCode and details.

All other errors (including non-Error values like strings or arrays) provide
only basic information: statusCode and message set to status name from HTTP
specification.

Debug mode

When in debug mode, HTTP responses include all properties provided by the error.

For errors that are not an object, their string value is returned in
message field.

When a method fails with an array of errors (e.g. bulk create), HTTP the response
contains still a single wrapper error with details set to the original array
of errors.

An example of an error response when an array of errors was raised:

{
  error: {
    statusCode: 500,
    name: 'ArrayOfErrors',
    message: 'Failed with multiple errors, see `details` for more information.',
    details: [
      { name: 'Error1', message: 'expected error', statusCode: 500, stack: '<stacktrace>' },
      { name: 'Error2', message: 'expected error2', statusCode: 500, stack: '<stacktrace>'}
    ]
  }
}

Please see Related code change here, and new strong-error-handler here.

Type converters replace Dynamic API

The Dynamic component was removed in favour of type converters and a
registry.

The following APIs were removed:

RemoteObjects.convert(name, fn)
remoteObjectsInstance.convert(name, fn)
RemoteObjects.defineType(name, fn)
remoteObjectsInstance.defineType(name, fn)

Two new APIs are added as a replacement:

remoteObjectsInstance.defineType(name, converter)
remoteObjectsInstance.defineObjectType(name, factoryFn)

See the API docs and
pull request #343
for more details.

Conversion and coercion of input arguments

We have significantly reworked conversion and coercion of input arguments
when using the default REST adapter. The general approach is to make both
conversion and coercion more strict. When we are not sure how to treat
an input value, we rather return HTTP error 400 Bad Request than coerce
the value incorrectly.

Most notable breaking changes:

  • null value is accepted only for "object", "array" and "any"
  • Empty string is coerced to undefined to support ES6 default arguments
  • JSON requests providing scalar values for array-typed argument are
    rejected
  • Empty value is not converted to an empty array
  • Array values containing items of wrong type are rejected. For
    example, an array containing a string value is rejected when
    the input argument expects an array of numbers.
  • Array items from JSON requests are not coerced. For example,
    [true] is no longer coerced to [1] for number arrays,
    and the request is subsequently rejected.
  • Deep members of object arguments are no longer coerced. For example,
    a query like ?arg[count]=42 produces { count: '42' } now.
  • "any" coercion preserves too large numbers as a string, to prevent
    losing precision.
  • Boolean types accepts only four string values:
    'true', 'false', '0' and '1'
    Values are case-insensitive, i.e. 'TRUE' and 'FaLsE' work too.
  • Date type detects "Invalid Date" and rejects such requests.
  • When converting a value coming from a string source like querystring
    to a date, and the value is a timestamp (i.e. an integer), we treat
    the value as a number now. That way the value "0" always produces
    "1970-01-01T00:00:00.000Z" instead of some date around 1999/2000/2001
    depending on server timezone.
  • Array values are not allowed for arguments of type object.

Hopefully this change should leave most LoopBack applications (and clients)
unaffected. If your start seeing unusual amount of 400 error responses after
upgrading to LoopBack 3.x, then please check the client code and ensure it
correctly encodes request parameters.

See the following pull requests for more details:

Serialization of Date values in responses

The format of date values has been changed from the output of .toString(),
which produces values in local timezone, to the output of .toJSON(), which
produces values in GMT.

For example, let's take new Date(0) returned for dateArgument.

In strong-remoting 2.x, this value is converted to the following response
when running on a computer in the Central-European timezone:

{
  "dateArgument": {
    "$type": "date",
    "$data": "Thu Jan 01 1970 01:00:00 GMT+0100 (CET)"
  }
}

In strong-remoting 3.x, the same value is converted to the following response
regardless of the server timezone settings:

{
  "dateArgument": {
    "$type": "date",
    "$data": "1970-01-01T00:00:00.000Z"
  }
}

See pull request #344 for more details.

CORS is no longer enabled

There are two types of applications that are commonly built using LoopBack,
and each type has different requirements when it comes to CORS.

  1. Public facing API servers serving a variety of clients.
    CORS should be enabled to allow 3rd party sites to access the API server
    living on a different domain.
  2. API+SPA sites where both API and the SPA client live on the same domain
    and the API should be locked down to serve only its own browser clients,
    i.e. CORS must be disabled.

By enabling CORS by default, we were making API+SPA sites vulnerable by default.

In strong-remoting 3.x, we removed the built-in CORS middleware and pushed
the responsibility for enabling and configuring CORS back to application
developers.

Note that this does not affect LoopBack applications scaffolded by a recent
version of slc loopback or apic loopback, as these applications don't rely
on the built-in CORS middleware and configure CORS explicitly
in server/middleware.json.

If your application relies on the built-in CORS middleware that used to be
provided by strong-remoting/loopback, then please make the following changes
to upgrade your application:

  1. npm install --save cors

2a) If you are using loopback-boot, then add the following entry to your
server/middleware.json file:

{
  // ...
  "initial": {
    // ...
    "cors": {
      "params": {
        "origin": true,
        "credentials": true,
        "maxAge": 86400
      }
    },
    // ...
  },
  // ...
}

2b) If you are not using loopback-boot, then configure the cors middleware
in your server script:

// ...

var cors = require('cors');
app.use(cors({ origin: true, credentials: true, maxAge: 86400 }));

// ...

It's important to add the cors middleware early, before any request-parsing
middleware.

See pull request #352 and
Security considerations
for more details.