@funnelchat-hq/sandbox 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A local server that imitates the Funnelchat public API and sends signed webhooks, with no real effects: nothing goes out to WhatsApp, nothing is charged, everything stays in memory. Build and test your integration without an account.
4
4
 
5
- It imitates production on 06-11: accounts v1.5.97, conversations v1.19.37, communities v2.35.7, platform-gate v0.9.1 and the API edge v0.3.0.
5
+ It comes from the spec the developer portal publishes, v0.6.14: the same operations and events (see [Pending of the spec](#pending-of-the-spec)). What it simulates with state follows production as it was checked on 06-11.
6
6
 
7
7
  ## Try it in 3 minutes, without an account
8
8
 
@@ -129,7 +129,7 @@ npx funnelchat-sandbox
129
129
  ```
130
130
 
131
131
  ```
132
- funnelchat-sandbox 0.4.0 at http://127.0.0.1:4010
132
+ funnelchat-sandbox 0.5.0 at http://127.0.0.1:4010
133
133
  API: http://127.0.0.1:4010/api/v1/... (Authorization: Bearer 1|fc_live_sandbox, or any well-formed key)
134
134
  panel: http://127.0.0.1:4010/_sandbox
135
135
  example numbers: 3 (official, with the 24-hour window) and 4 (QR, no window); contact 2001; template 45
@@ -149,12 +149,12 @@ npx funnelchat-sandbox
149
149
 
150
150
  ```bash
151
151
  docker build -t funnelchat-sandbox .
152
- docker run --rm -p 4010:4010 funnelchat-sandbox
152
+ docker run --rm -p 127.0.0.1:4010:4010 funnelchat-sandbox
153
153
  # with your configuration:
154
- docker run --rm -p 4010:4010 -v "$PWD/funnelchat-sandbox.config.json:/app/funnelchat-sandbox.config.json:ro" funnelchat-sandbox
154
+ docker run --rm -p 127.0.0.1:4010:4010 -v "$PWD/funnelchat-sandbox.config.json:/app/funnelchat-sandbox.config.json:ro" funnelchat-sandbox
155
155
  ```
156
156
 
157
- Inside the container it listens on `0.0.0.0`. If your webhook receiver runs on your machine, from the container it is `http://host.docker.internal:<port>` (on Linux, add `--add-host=host.docker.internal:host-gateway`).
157
+ Inside the container it listens on `0.0.0.0`; `127.0.0.1:` publishes the port only on your machine (with `-p 4010:4010` the whole network reaches the sandbox, which takes no key on `/_sandbox`). If your webhook receiver runs on your machine, from the container it is `http://host.docker.internal:<port>` (on Linux, add `--add-host=host.docker.internal:host-gateway`).
158
158
 
159
159
  **From your tests**, with no other process:
160
160
 
@@ -582,7 +582,7 @@ You subscribe with the same routes the SDKs and the n8n trigger use (the unified
582
582
  | `DELETE /api/v1/webhooks/{id}` | Deletes it (`204`). |
583
583
  | `POST /api/v1/webhooks/{id}/test` | Sends a signed `webhook.test` now and answers `{succeeded, response_status, error, duration_ms, delivery_id}`. |
584
584
  | `GET /api/v1/webhooks/{id}/deliveries` | The history: one record per attempt, newest first. |
585
- | `GET /api/v1/webhooks/events` | The catalog of accounts in production: 59 types (54 `active`), with `area`, `status`, `is_critical`, `label`, `description` and `example`. |
585
+ | `GET /api/v1/webhooks/events` | The event catalog of the spec: the 55 types of the `events` enum of `POST /api/v1/webhooks` and `webhook.test` (not subscribable), all `active`, each with `area`, `status`, `description`, `subscribable` and `example`. |
586
586
 
587
587
  **Each delivery** is a `POST` with the portal's envelope and these headers:
588
588
 
@@ -632,7 +632,7 @@ These routes take no key: they belong to the sandbox, not to the API.
632
632
  | `POST /_sandbox/devices/{id}/health` | The health of an official number changes: `{"state": "limited"}` (`available`, `limited`, `blocked`, `unknown`) emits `device.health_changed`, only if it changed. |
633
633
  | `POST /_sandbox/numbers`, `GET /_sandbox/numbers` | The quota of the account: `{"limit": 3, "included": true, "trial": false, "meta_connect": true, "links_per_minute": 10}` (`meta_connect` turns the Meta sign-up on or off). `GET` also says how many places are in use and which QR numbers are pending. |
634
634
  | `POST /_sandbox/templates/{id}/status` | Meta approves or rejects a template: `{"status": "REJECTED", "reason": "…"}` emits `template.status_changed`. |
635
- | `POST /_sandbox/events/{type}` | Any event of the catalog, with its example; what you send in `{"data": {…}}` overrides it, and `{"workspace_id": 2}` sends it to that workspace (the default one otherwise). `GET /_sandbox/events` lists them. `group.message_received` and `group.reaction_added` go out with the shape communities emits (`message.uid` and every key of the message), not with the example of the accounts catalog, which uses `message.id`. |
635
+ | `POST /_sandbox/events/{type}` | Any event of the catalog, with its example; what you send in `{"data": {…}}` overrides it, and `{"workspace_id": 2}` sends it to that workspace (the default one otherwise). `GET /_sandbox/events` lists them. The example is the one of the catalog, the spec's. |
636
636
  | `POST /_sandbox/window` | Opens or closes the window by hand: `{"contact_id": 2001, "device_id": 3, "open": false}`. |
637
637
  | `POST /_sandbox/workspaces`, `GET /_sandbox/workspaces` | A [workspace](#workspaces) with a key that only sees it: `{"name": "Cliente B"}` answers `201 {workspace, key}`. `POST /_sandbox/keys` with `{"workspace_id": 2}` mints another key (of the default workspace without `workspace_id`); with `"permissions": [...]` it has only those (the sandbox checks just `communities:operate`, to import a group). |
638
638
  | `POST /_sandbox/reset` | Back to the initial state. |
@@ -721,7 +721,7 @@ And at `http://localhost:4010/_sandbox` you see each request and each delivery w
721
721
  - **The shape-only routes** answer the shape of the spec, always the same: it does not match your data, a write keeps nothing and no event goes out. See [With state or shape only](#with-state-or-shape-only).
722
722
  - **The validation messages** are Laravel's in Spanish, built from the schema: they may not match the API's word for word (decide by the HTTP status and by `error`, not by `message`). The real API's validation `422` is Laravel's, `{message, errors}`, without `error`; the sandbox adds `"error": "validation_failed"`.
723
723
  - **Webhook URLs** may be `http` and local, without the real API's protection against internal destinations (try `--require-https`). Do not expose the sandbox to the internet: the `/_sandbox` routes take no key and the sandbox `POST`s to any URL subscribed to it.
724
- - **The catalog** is rendered from the code of accounts v1.5.97 (`spec/catalog.json`, with `scripts/transcribe-catalog.php`), not read from a response: if accounts changes it, it has to be rendered again.
724
+ - **The event catalog** is what the spec documents (`generated/catalog.json`, transcribed by `npm run generate`): fields of production's catalog the spec does not document, such as `is_critical` and `label`, are not there.
725
725
  - **Groups have state only for the number's live list, the list, one group, the import and the send of a message** (the action is `completed` after the delays of `sent` and `delivered`; it never fails on its own). The live list is never cached (production keeps it a minute for a key). The other group actions, the campaigns and the rest of the groups' routes answer shape only, and their events are fired from `/_sandbox`. There are no official groups (the account is not in their beta), no `too_many_pending_actions` and no rate limit of 30 sends per minute.
726
726
  - **Workspaces confine** numbers, sign-up links, webhook subscriptions, contacts, conversations, messages, templates and groups. Tags, custom fields and forms are not confined in the sandbox. A contact belongs to one workspace, and the phone stays unique in the account: `POST /api/v1/contacts`, `update-or-create` and a send by phone with a phone of another workspace answer `404`.
727
727
  - **Forms do not talk to Meta:** `sync` and `publish` succeed unless the linter finds an error, and the linter is a short version of the real one (screens without a title, without an ending, without questions, with a connection to the server). The metrics (`…/metrics`) answer shape only, and there are no dynamic forms.
@@ -734,12 +734,12 @@ And at `http://localhost:4010/_sandbox` you see each request and each delivery w
734
734
 
735
735
  ## Pending of the spec
736
736
 
737
- The spec (`spec/funnelchat.json`) is the one the portal publishes, v0.6.13 (https://developers-portal.funnelchat.app/specs/funnelchat.json, 11-11), made from accounts v1.6.41, conversations v1.19.91 and communities v2.35.28: only the operations and events the portal shows. **The sandbox offers only that:** a layer corrects shapes where the spec says otherwise than production answers, but never adds an operation or an event (the generator refuses it and says so). The corrections are in two layers, which the next regeneration drops on its own once the spec carries them:
737
+ The spec (`spec/funnelchat.json`) is the one the portal publishes, v0.6.14 (https://developers-portal.funnelchat.app/specs/funnelchat.json, 13-11): only the operations and events the portal shows. **The sandbox offers only that:** a layer corrects shapes where the spec says otherwise than production answers, but never adds an operation or an event (the generator refuses it and says so). The corrections are in two layers, which the next regeneration drops on its own once the spec carries them:
738
738
 
739
- - `spec/pending.json`: the pending layer of the Node SDK, copied unchanged (`funnelchat-node` `feat/todas-las-funciones` @ `bde8679`). Guarded by JSON Pointer conditions: the send answers, `devices/{device}/qr`, the template list, `PUT webhooks/{id}`, `GET groups/actions/{action}`, and the shapes of the number, group, billing and message events.
739
+ - `spec/pending.json`: the pending layer of the Node SDK (`funnelchat-node` @ `bde8679`), with the corrections of 13-11 made here ahead of the SDK: `answers[].label` of `whatsapp_form.completed` is always a string, `message.type` of `message.received` accepts types the spec does not list yet, and its sources say what portal v0.6.14 shows (its note in `spec/procedencia.json` lists them). Guarded by JSON Pointer conditions: the send answers, `devices/{device}/qr`, the template list, `PUT webhooks/{id}`, `GET groups/actions/{action}`, and the shapes of the number, group, billing, form and message events.
740
740
  - `spec/pending-sandbox.json`: what the sandbox corrects on top of it, with its sources inside.
741
741
 
742
- Besides, `spec/catalog.json` is the event catalog of accounts v1.5.97, with its provenance.
742
+ The event catalog (`generated/catalog.json`) comes out of the merged spec, with the same command: see [Regenerating](#regenerating).
743
743
 
744
744
  What `spec/pending-sandbox.json` still corrects:
745
745
 
@@ -756,7 +756,7 @@ Everything generated comes from a single script:
756
756
  # With a new spec of the portal: always the one it publishes, only the operations and events the portal shows (the
757
757
  # tools are generated only from what the portal publishes). Download it, and say with --origin which version of the
758
758
  # portal it is, for example {"url": "https://developers-portal.funnelchat.app/specs/funnelchat.json",
759
- # "portal_version": "v0.6.13", "domains": [{"domain": "accounts", "tag": "v1.6.41"}, …]}:
759
+ # "portal_version": "v0.6.14"}; it is the only place the version is written, and spec/procedencia.json keeps it:
760
760
  curl -sO https://developers-portal.funnelchat.app/specs/funnelchat.json
761
761
  npm run generate -- --spec funnelchat.json --origin <that JSON>
762
762
 
@@ -770,17 +770,18 @@ npm run generate
770
770
  npm run generate:check
771
771
  ```
772
772
 
773
- The event catalog (`spec/catalog.json`) is rendered from accounts' code with `scripts/transcribe-catalog.php` (PHP 8.2 or later, no Laravel needed); its header says how.
773
+ The event catalog comes out of the same command, so it is never transcribed by hand: the types are the `events` enum of `POST /api/v1/webhooks`, in its order, and each entry is what the spec documents for the type in `webhooks`, its example always the whole envelope (a correction that brings only `data` takes `id`, `version` and `occurred_at` from the portal's example); `webhook.test`, which only the test route sends, goes last and is not subscribable. A type of the enum that `webhooks` does not document stops the generation.
774
774
 
775
775
  What it does:
776
776
 
777
777
  1. With `--spec` or `--pending`, it copies the file to `spec/` and writes in `spec/procedencia.json` the repository, the branch, the commit, the date and the `sha256` (from git, or from `--origin` for what git cannot say).
778
- 2. It applies over the snapshot `spec/pending.json` and then `spec/pending-sandbox.json`, but **only what the snapshot lacks**, and never an operation or an event the snapshot does not carry: it refuses those and lists them. At the end it lists what the snapshot already carries, so you can delete it.
778
+ 2. It applies over the snapshot `spec/pending.json` and then `spec/pending-sandbox.json`, but **only what the snapshot lacks**, and never an operation or an event the snapshot does not carry: it refuses those and lists them. At the end it lists what the snapshot already carries, so you can delete it. An event a layer types without an example keeps the portal's example.
779
779
  3. It writes `generated/openapi.json`, the spec the sandbox validates and answers with.
780
+ 4. It writes `generated/catalog.json`, the event catalog, from that spec.
780
781
 
781
782
  **A handler whose operation is not in the spec is not reached:** the route answers `404 route_not_found`, like a route the API does not have. After a regeneration, `GET /_sandbox/operations` says what has state and what answers [shape only](#with-state-or-shape-only).
782
783
 
783
- **The current spec** is the one the portal publishes, v0.6.13, at https://developers-portal.funnelchat.app/specs/funnelchat.json (sha256 `a7b480743848dabccbea2a1f2314263ca070316eeed97aab4a6f09f171c80e5a`, as served on 12-11): 209 paths, 253 operations and 57 events (56 types) of accounts v1.6.41, conversations v1.19.91 and communities v2.35.28, in 30 groups: 1 of «Primer uso», 9 of «Funciones de WhatsApp» and 20 of «Funciones de Funnelchat». The detail is in `spec/procedencia.json`.
784
+ **The current spec** is the one the portal publishes, v0.6.14, at https://developers-portal.funnelchat.app/specs/funnelchat.json (sha256 `37c54a5aad4cc467e088af50294012484692ef668714dcdd748babc7314b587f`, as served on 13-11): 209 paths, 253 operations and 57 events (56 types: the 55 of the `events` enum and `webhook.test`, which two areas document), in 30 groups: 1 of «Primer uso», 9 of «Funciones de WhatsApp» and 20 of «Funciones de Funnelchat». The detail is in `spec/procedencia.json`.
784
785
 
785
786
  ## Development
786
787
 
@@ -789,16 +790,16 @@ npm install
789
790
  npm run generate:check # generated/ is up to date
790
791
  npm run typecheck # tsc
791
792
  npm run lint
792
- npm test # vitest, no network: the receivers are local servers
793
+ npm test # vitest (Node 20.19 or later), no network: the receivers are local servers
793
794
  npm run build # dist/ in ESM, with .d.ts
794
795
  ```
795
796
 
796
797
  The tests verify the deliveries with unchanged copies of the Node SDK's `verify` and of the portal's snippet (`test/vendor/`, with their provenance). If the `funnelchat-node` repo is next to this one (or in `FUNNELCHAT_NODE_SDK`), they also run against its current code, including the whole client pointed at the sandbox.
797
798
 
798
799
  ```
799
- spec/ the spec, its provenance, the two pending layers and the event catalog
800
- scripts/ generate.mjs, the only generator, and transcribe-catalog.php for the catalog
801
- generated/ the merged spec (not edited)
800
+ spec/ the spec, its provenance and the two pending layers
801
+ scripts/ generate.mjs, the only generator
802
+ generated/ the merged spec and the event catalog (not edited)
802
803
  src/openapi/ routes, validation and the shape-only answers, from the spec
803
804
  src/handlers/ the routes with state (numbers, links, contacts, messages, forms, groups, webhooks); index.ts is the table
804
805
  src/forms/ the form templates
@@ -809,7 +810,9 @@ src/sandbox/ the /_sandbox routes and the panel
809
810
  test/ vitest
810
811
  ```
811
812
 
812
- **Publishing:** push a `vX.Y.Z` tag on `main` that matches the `version` in `package.json`; GitHub Actions publishes it through npm Trusted Publishing (`.github/workflows/publish.yml`).
813
+ **Checks:** every pull request and every push to `main` run `.github/workflows/ci.yml`: `generate:check`, `typecheck`, `lint`, the tests and the build on Node 20.19, 22 and 24, and the built package started on Node 20.11, the minimum of `engines` (the test tools need 20.19).
814
+
815
+ **Publishing:** push a `vX.Y.Z` tag, on a commit of `main`, that matches the `version` in `package.json`. `.github/workflows/publish.yml` stops before publishing if the tag's commit is not on `main`, runs the same checks and the tests, and publishes through npm Trusted Publishing. A pre-release tag (`vX.Y.Z-rc.N`, `vX.Y.Z-beta.N`) goes out under the `next` dist-tag, never as `latest`.
813
816
 
814
817
  ## License
815
818