{"id":20770801,"url":"https://github.com/ably/asset-tracking-backend-demo","last_synced_at":"2025-10-19T15:32:43.508Z","repository":{"id":39494746,"uuid":"506946787","full_name":"ably/asset-tracking-backend-demo","owner":"ably","description":"Incubating.","archived":false,"fork":false,"pushed_at":"2023-07-19T18:04:13.000Z","size":778,"stargazers_count":2,"open_issues_count":7,"forks_count":2,"subscribers_count":24,"default_branch":"main","last_synced_at":"2025-04-09T14:12:52.464Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ably.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-24T09:09:08.000Z","updated_at":"2023-08-10T01:13:38.000Z","dependencies_parsed_at":"2025-01-18T07:10:20.862Z","dependency_job_id":"f1135093-89ef-4a2c-9a99-8b585386c3bc","html_url":"https://github.com/ably/asset-tracking-backend-demo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ably/asset-tracking-backend-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fasset-tracking-backend-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fasset-tracking-backend-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fasset-tracking-backend-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fasset-tracking-backend-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ably","download_url":"https://codeload.github.com/ably/asset-tracking-backend-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fasset-tracking-backend-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277315624,"owners_count":25797669,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-28T02:00:08.834Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T12:12:01.290Z","updated_at":"2025-09-28T02:30:36.362Z","avatar_url":"https://github.com/ably.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ably Asset Tracking Backend Demo\n\n[![.github/workflows/check.yml](https://github.com/ably/asset-tracking-backend-demo/actions/workflows/check.yml/badge.svg)](https://github.com/ably/asset-tracking-backend-demo/actions/workflows/check.yml)\n\n_[Ably](https://ably.com) is the platform that powers synchronized digital experiences in realtime. Whether attending an event in a virtual venue, receiving realtime financial information, or monitoring live car performance data – consumers simply expect realtime digital experiences as standard. Ably provides a suite of APIs to build, extend, and deliver powerful digital experiences in realtime for more than 250 million devices across 80 countries each month. Organizations like Bloomberg, HubSpot, Verizon, and Hopin depend on Ably’s platform to offload the growing complexity of business-critical realtime data synchronization at global scale. For more information, see the [Ably documentation](https://ably.com/documentation)._\n\n## Overview\n\nThis demo presents a mock backend service with functionality matching that expected for the typical use case for\n[Ably's Asset Tracking solution](https://ably.com/solutions/asset-tracking),\nbeing the tracking of deliveries to customers.\nThose deliveries could be food, groceries or other packages ordered for home delivery.\n\n### Related Demos\n\nThis demo backend service has been designed to interoperate with the following demo apps:\n\n- **Rider**: Used by delivery riders / drivers. The publisher.\n  - [Android](https://github.com/ably/asset-tracking-android-rider-app-demo)\n  - [iOS](https://github.com/ably/asset-tracking-ios-rider-app-demo)\n- **Subscriber**: Used by customers / consumers, to track their delivery.\n  - [Android](https://github.com/ably/asset-tracking-android-customer-app-demo)\n  - [iOS](https://github.com/ably/asset-tracking-ios-customer-app-demo)\n  - [Web](https://github.com/ably/asset-tracking-web-customer-app-demo)\n\n### Delivery Lifecycle\n\nIn the simplest scenario, where a Rider is only carrying a single Customer order at any given time:\n\n1. Customer places order for Delivery and starts observing this Delivery, though they will not see any Location updates yet\n2. Rider requests, or is assigned, the Delivery\n3. Rider travels to the Merchant\n4. Merchant gives the Delivery to the Rider\n5. Rider starts transmitting Location updates for this Delivery, meaning that the Customer starts seeing Location updates now\n6. Rider travels from Merchant to Customer with the Delivery\n7. Rider gives the Delivery to the Customer\n8. Rider stops transmitting Location updates for this Delivery\n9. Customer stops tracking Location updates\n\nA real delivery backend service will have states for the Delivery order including stages like \"placed\", \"accepted by Merchant\" and \"being prepared\".\nThis demo backend service does not emulate those stages, to keep things simpler, instead focussing on the core Delivery Location tracking element.\n\nIn respect of the state of the Rider, in the context of a particular Delivery, from the perspective of the Customer, referencing the steps from the simplest scenario outlined above:\n\n| Rider State | Emulated by this demo backend service? |\n| ----------- | -------------------------------------- |\n| accepted the Delivery | Yes, step 2 |\n| waiting at the Merchant | No |\n| has picked up your Delivery | No |\n| is on the way | Yes, step 5 |\n| is nearby | No |\n| is here | Yes, step 8 |\n\nThe Customer expects to see a map showing:\n\n- The destination\n- The Merchant\n- The Rider\n\n## Runtime Requirements\n\nA\n[Firebase](https://firebase.google.com/)\naccount with support for\n[Firestore](https://firebase.google.com/products/firestore)\nand\n[Functions](https://firebase.google.com/products/functions)\n(which probably means a paid plan).\n\n### Environment Variable and Secret Names\n\nTo make this codebase more navigable alongisde associated service configurations,\nwe've conformed naming of secrets in the three locations you'll find them, that is:\n\n1. As secrets [configured via the Firebase CLI](https://firebase.google.com/docs/functions/config-env#create-secret)\n2. As environment variables fed into the Node.js process at runtime\n3. In the source code, populated from `process.env`\n  (also defined as static strings as members of the `SECRET_NAMES` array defined in [`index.js`](functions/index.js))\n\n| Secret Name              | Description                                                                                           |\n|--------------------------|-------------------------------------------------------------------------------------------------------|\n| `ABLY_API_KEY_RIDERS`    | Used to sign JSON web tokens returned to `rider` users by this service.                               |\n| `ABLY_API_KEY_CUSTOMERS` | Used to sign JSON web tokens returned to `customer` users by this service.                            |\n| `MAPBOX_ACCESS_TOKEN`    | Returned to Rider apps in [Assign Order](#assign-order) responses.                                    |\n| `GOOGLE_MAPS_API_KEY`    | Returned to Customer apps in [Create Order](#create-order) responses.                                 |\n| `INITIAL_USER_PASSWORD`  | Creates an initial `admin` user with this password, used for creating `rider` and `customer` accounts |\n\n### Ably API Key Capabilities\n\n`ABLY_API_KEY_RIDERS` must have been created with the following capabilities:\n\n- **Publish** - publish messages to channels\n- **Subscribe** - subscribe to receive messages and presence state changes on channels\n- **Presence** - register presence on a channel (enter, update and leave)\n\n`ABLY_API_KEY_CUSTOMERS` must have been created with the following capabilities:\n\n- **Publish** - publish messages to channels\n- **Subscribe** - subscribe to receive messages and presence state changes on channels\n- **History** - retrieve message and presence state history on channels\n- **Presence** - register presence on a channel (enter, update and leave)\n\nIt is recommended, for best practice in respect of security architecture, to:\n\n- restrict each key to just the set of capabilities detailed above for it\n- resource restrict each key to only be able to access channels (not queues)\n- consider enabling [revocable tokens](https://ably.com/docs/core-features/authentication#token-revocation)\n\nSee [Ably Token](#ably-token) for details of the capabilities given to tokens signed by this service with these keys.\n\n## Deployment\n\nThe following command builds the functions and pushes them out to Firebase:\n\n    firebase deploy --only functions\n\nas described in:\n[Firebase: Get started: Deploy functions to a production environment](https://firebase.google.com/docs/functions/get-started#deploy-functions-to-a-production-environment)\n\n## Development and Testing\n\nIn common with most Firebase projects,\n[the contents of the `functions` folder](functions/)\nis a [Node.js](https://nodejs.org/) application using [npm](https://www.npmjs.com/) for dependency management.\nThis means that the `npm` commands should be utilised within that folder,\nwhile `firebase` commands are generally used from root (though they do work from here too).\n\n### Creating Users\n\nAll endpoints presented by this demo backend service require [HTTP Basic authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme).\n\nCredentials - `user-id` (username) and `user-pass` (password) - are validated from records in the Firestore database.\nThere must be a document per user in the the root `users` collection, which can be navigated to in the Firebase Console view of the Firebase project's Cloud Firestore.\nThe `users` collection in the Console can be found at:\n\n    https://console.firebase.google.com/project/\u003cfirebase-project-name\u003e/firestore/data/~2Fusers\n\nOr if you've got a new Firebase project, for which the Firestore database exists but you've not yet created the `users` root collection, then visit here to do that:\n\n    https://console.firebase.google.com/project/\u003cfirebase-project-name\u003e/firestore/data/~2F\n\nFor each user document in the `users` collection:\n\n- The Document ID is the `user-id` (username)\n- The document contents must consist of at least the following fields, both with type of `string`:\n  | Field Name | Field Value |\n  | ---------- | ----------- |\n  | `password` | The `user-pass` (password), in plain text (for simplicity). |\n  | `type` | Either '`rider`' or '`customer`'. |\n\nFor example, here's the Firebase Console view of the document for a user with `user-id` '`username`' and `user-pass` '`password`':\n\n![Firestore User Document Example](/resources/images/firestore-user.png)\n\n### Testing with the Local Emulator\n\nThe following command builds the functions and serves them locally:\n\n    firebase emulators:start\n\nas described in\n[Firebase: Get started: Emulate execution of your functions](https://firebase.google.com/docs/functions/get-started#emulate-execution-of-your-functions)\n\nIf it emits the following `functions` warning to the console:\n\n```\ni  emulators: Starting emulators: functions\n⚠  functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: auth, firestore, database, hosting, pubsub, storage\n```\n\nIndicating that HTTP requests made against this functions emulator instance will use the live Firestore database.\nThis is probably fine, perhaps preferable for some testing, but is worth noting as caution should be exercised.\n\nIt also emits information to the console, which will look something like:\n\n```\n┌───────────┬────────────────┬─────────────────────────────────┐\n│ Emulator  │ Host:Port      │ View in Emulator UI             │\n├───────────┼────────────────┼─────────────────────────────────┤\n│ Functions │ localhost:5001 │ http://localhost:4000/functions │\n└───────────┴────────────────┴─────────────────────────────────┘\n```\n\nwhere you can visit port `4000` from your browser for access to function runtime logs, amongst much more.\nThe functions themselves are hosted at port `5001`, so can be tested there, for example:\n\n    curl --verbose \\\n      --user \"username:password\" \\\n      http://localhost:5001/\u003cfirebase-project-name\u003e/\u003cfirebase-region\u003e/deliveryService\n      \n\nIt's also worth noting that the emulator supports automatic reloading, so the effect of changes you make to the source code files while the emulator is running will immediately be available to observe.\nThis can make for a very productive debugging experience.\n\n## REST API\n\nThe content type for requests and responses is `application/json`, with root of [JSON](https://www.json.org/) `object` type.\n\n### Create Order\n\n`POST /orders`\n\nUsed by the Customer app to kick off a new Delivery requirement.\nCreates a new order with a unique order identifier.\n\nRequest properties:\n\n- `from`: [Location](#location-type) of the Merchant.\n- `to`: [Location](#location-type) of the Customer.\n\nSuccessful response status code: `201` Created\n\nResponse properties:\n\n- `orderId`: The unique order identifier for this new Delivery. A positive integer.\n- `ably.token`: The JSON Web Token (JWT) to be used to subscribe for Location updates for this new Delivery.\n- `googleMaps.apiKey`: The API key to be used if rendering maps using Google's engine.\n\nThe `googleMaps.apiKey` is static so apps do **not** need to handle the scenario that it changes from request to request.\nThis means that values received from subsequent calls may be safely ignored, with the app's UI continuing to use\nvisual components created using the key received from their first call to this endpoint or the\n[Get Google Maps](#get-google-maps) endpoint.\nThis key is likely needed for Android apps and will likely be ignored by iOS apps, in preference for using Apple maps.\n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/orders \\\n  --user \"username:password\" \\\n  --header \"Content-Type: application/json\" \\\n  --request POST \\\n  --data '{\"from\":{\"latitude\":1,\"longitude\":2},\"to\":{\"latitude\":3,\"longitude\":4}}'\n```\n\nExample response (prettified):\n\n```json\n{\n  \"orderId\": 3,\n  \"ably\": {\n    \"token\": \"\u003cSECRET_REDACTED\u003e\"\n  },\n  \"googleMaps\": {\n    \"apiKey\": \"\u003cSECRET_REDACTED\u003e\"\n  }\n}\n```\n\nSee also: [Ably Token](#ably-token)\n\n### Get Unassigned Orders\n\n`GET /orders`\n\nUsed by the Rider app to view all currently unassigned orders.\n\nSuccessful response status code: `200` OK\n\nResponse properties:\n\n- `orderIds`: An array of unique order identifiers for all currently unassigned orders \n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/orders \\\n  --user \"username:password\" \n```\n\nExample response (prettified):\n\n```json\n{\n  \"orderIds\": [4, 8, 15, 16, 23, 42]\n}\n```\n\nSee also: [Ably Token](#ably-token)\n\n### Assign Order\n\n`PUT /orders/\u003corderId\u003e`\n\nUsed by the Rider app to self-assign a Delivery requirement.\nModifies an unassigned order to assign it to this rider.\n\nSuccessful response status code: `201` Created\n\nResponse properties:\n\n- `customerUsername`: The username of the Customer who created this Delivery requirement.\n- `from`: [Location](#location-type) of the Merchant.\n- `to`: [Location](#location-type) of the Customer.\n- `ably.token`: The JSON Web Token (JWT) to be used to publish Location updates for this Delivery.\n- `mapbox.token`: The access token to be used for the Mapbox Navigation enhanced location engine.\n\nThe `mapbox.token` is static so apps do **not** need to handle the scenario that it changes from request to request.\nThis means that an Ably Asset Tracking SDK publisher instance may be created using the token received from the\nfirst call to this endpoint or the [Get Mapbox](#get-mapbox) endpoint, with the token value received from subsequent\ncalls to this endpoint safely ignored.\n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/orders/\u003corderId\u003e \\\n  --user \"username:password\" \\\n  --request PUT\n```\n\nExample response (prettified):\n\n```json\n{\n  \"to\": {\n    \"longitude\": 4,\n    \"latitude\": 3\n  },\n  \"customerUsername\": \"\u003cSECRET_REDACTED\u003e\",\n  \"from\": {\n    \"longitude\":2,\n    \"latitude\":1\n  },\n  \"ably\": {\n    \"token\": \"\u003cSECRET_REDACTED\u003e\"\n  },\n  \"mapbox\": {\n    \"token\": \"\u003cSECRET_REDACTED\u003e\"\n  }\n}\n```\n\nThis endpoint can safely be called multiple times and, as such, can be considered idempotent.\nThe response includes only fields were present in the database before the request was made, which means that subsequent\ncalls to this endpoint by the same rider for the same order will observe an additional field in the response by the name\nof `riderUsername` - this is expected behaviour, being a side effect of the simplistic implementation of this demo\nbackend service.\n\nSee also: [Ably Token](#ably-token)\n\n### Delete Order\n\n`DELETE /orders/\u003corderId\u003e`\n\nUsed by either the Rider app or the Customer app to remove, or otherwise declare finished, a Delivery requirement.\nDeletes an assigned order from the database.\n\nSuccessful response status code: `200` OK\n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/orders/\u003corderId\u003e \\\n  --user \"username:password\" \\\n  --request DELETE\n```\n\n### Get Google Maps\n\n`GET /googleMaps`\n\nMay be used by the Customer app to obtain the API key for use with Google Maps, typically in advance of calls to any\nother endpoint.\n\nSuccessful response status code: `200` OK\n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/googleMaps \\\n  --user \"username:password\"\n```\n\nExample response (prettified):\n\n```json\n{\n  \"apiKey\": \"\u003cSECRET_REDACTED\u003e\"\n}\n```\n\nThis same, static key is also returned in responses from the [Create Order](#create-order) endpoint.\n\n### Get Mapbox\n\n`GET /mapbox`\n\nMay be used by the Rider app to obtain the access token for use with Mapbox, typically in advance of calls to any\nother endpoint.\n\nSuccessful response status code: `200` OK\n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/mapbox \\\n  --user \"username:password\"\n```\n\nExample response (prettified):\n\n```json\n{\n  \"token\": \"\u003cSECRET_REDACTED\u003e\"\n}\n```\n\nThis same, static key is also returned in responses from the [Assign Order](#assign-order) endpoint.\n\n### Get Ably\n\nUsed by both the Rider app and the Customer app to request a new authentication token for use with the Ably service.\n\nApps will need to call this endpoint in response to a token request callback from the Ably SDK.\nThis will be in one of the following scenarios, relating to the auth token currently in use:\n\n- It has expired\n- It has insufficient capabilities:\n  Lack of permission to subscribe or publish, as applicable, to the channel for Location updates for a Delivery.\n\nSuccessful response status code: `200` OK\n\nExample request:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/ably \\\n  --user \"username:password\"\n```\n\nExample response (prettified):\n\n```json\n{\n  \"token\": \"\u003cSECRET_REDACTED\u003e\"\n}\n```\n\nSee also: [Ably Token](#ably-token)\n\n## REST API Error Responses\n\nService errors, in common with success responses, are returned with content type `application/json`,\nwith root of [JSON](https://www.json.org/) `object` type. This object has a single property named `error`, whose value\nis another object, representing the `Error`. The `Error` object will usually have a single property named `message`,\nwhose value is in a human readable form and is designed to be safe to display to the app user. The `message` property\nis intentionally not included for auth-related errors (i.e. `401` Unauthorized).\n\nExample request, designed to fail:\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/orders/999999999999 \\\n  --user \"username:password\" \\\n  --request DELETE\n```\n\nExample response (prettified):\n\n```json\n{\n  \"error\": {\n    \"message\": \"An order with id '999999999999' does not exist.\"\n  }\n}\n```\n\n## REST API Types\n\n### Location Type\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| `latitude` | number | **Required** - Must be a value between `-90.0` and `90.0`. |\n| `longitude` | number | **Required** - Must be a value between `-180.0` and `180.0`. |\n\n## HTTP Request Headers\n\nThese are:\n\n- accepted by all endpoints\n- here, primarily, to help us QA test the [related demo apps](#related-demos) against this demo backend service\n\n### Ably Token TTL Request Header\n\nOverrides the default time-to-live for returned tokens.\n\n- **Name**: `ably-token-ttl`\n- **Value**: Time to live, in seconds, which may not exceed the maximum (see [Ably Token](#ably-token))\n\nExample request, specifying a TTL of 1 minute (60 seconds):\n\n```bash\ncurl --verbose \\\n  https://\u003cfirebase-region\u003e-\u003cfirebase-project-name\u003e.cloudfunctions.net/deliveryService/ably \\\n  --user \"username:password\" \\\n  --header \"ably-token-ttl: 60\"\n```\n\n## Ably Token\n\nIssued with:\n\n- By default, a TTL (time-to-live) of 1 hour (3,600 seconds), which is the maximum duration supported by this service\n- [capability](https://ably.com/docs/core-features/authentication#capability-operations):\n  - granted for just the Ably [channels](https://ably.com/docs/realtime/channels) for orders which the authenticated user calling the endpoint needs to interact - where those channel names have the `tracking:` [channel namespace](https://ably.com/docs/realtime/channels#channel-namespaces) followed by the order identifier\n  - depending on the type of the authenticated user calling the endpoint which returned the token:\n    - **Rider**: `subscribe`, `publish` and `presence`\n    - **Customer**: `subscribe`, `publish`, `history` and `presence`\n- client identifier (`x-ably-clientId`) set to the `user-id` (username) of the authenticated user calling the endpoint which returned the token\n\nSee [Ably API Key Capabilities](#ably-api-key-capabilities), for signing key requirements.\n\nSee [Ably Token TTL Request Header](#ably-token-ttl-request-header), for details of the HTTP header which clients may send alongside their requests to override the default TTL.\nThis is useful for testing purposes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably%2Fasset-tracking-backend-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fably%2Fasset-tracking-backend-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably%2Fasset-tracking-backend-demo/lists"}