Floatplane Async Frontend API 4.0.13

Homepage: https://jman012.github.io/FloatplaneAPIDocs

This document describes the asynchronous/event-driven API layer of https://www.floatplane.com, a content creation and video streaming website created by Floatplane Media Inc. and Linus Media Group, where users can support their favorite creates via paid subscriptions in order to watch their video and livestream content in higher quality and other perks.

This API is specific to the frontend activities of the Floatplane website, which is responsible for pushing new-post notifications to the client, and is meant for a connection to www.floatplane.com. If you are looking for the chat/livestream async API layer of Floatplane (chat.floatplane.com), please visit this document instead.

Implementation Notes

This document is an AsyncAPI implementation on top of a Socket.IO connection. AsyncAPI does not have any specifics for Socket.IO, and so any automatic code-generation from this document may not work 100% out-of-the-box on a WebSocket connection. At best, it will provide all of the proper models needed for the different events and messages. But it may require some glue code to get it working properly with Sails. See the below section on the difference between Socket.IO, Sails, and Floatplane for this socket connection.

It is recommended for any client implementation to use a Socket.IO reference library implementation in your language of choice with this document. A version of this document for Sails would be preferred, but there is only a single client reference implementation (JavaScript), and the Sails layer on top of Socket.IO is fairly straightforward.

Technology Notes

Floatplane's backend primarily uses Sails and the website UI uses an Angular frontend along with Sails' socket connection for low-latency request/response and event-driven architecture. Sails is an MVC framework for making Node.js websites and APIs. Sails' socket connection is built on three tiers of technology that should be understood when implementing this API:

  1. Engine.IO (GitHub)
    1. This layer is responsible for abstracting the socket connection for reliability. It primarily uses WebSockets as the communication channel, but will fall back to HTTP long-polling if WebSockets are not available.
    2. It provides a rather simple protocol on top of the below items, usually prefixing a WebSocket frame with a single byte indicating the packet type.
    3. Engine.IO does have its own connection mechanism, and it would be best to have this implemented by a library rather than by hand. While it has a reference JavaScript/Node.JS implementation and a Java implementation, it is recommended to use a Socket.io library outlined below.
    4. On the wire, you'll see connection frames with the 0 prefix, pings/pongs as a single byte (2/3), or messages with the 4 prefix. If you are analyzing the WebSocket frames directly, it would be beneficial to familiarize yourself with the Engine.IO protocol.
  2. Socket.IO
    1. This builds on Engine.IO by adding reconnection, packet buffering, acknowledgements (request/response), broadcasting, and multiplexing (namespaces) features.
    2. Note that Floatplane is using Socket.IO version v2. Not the latest v4. This may affect which client Socket.IO library implementation you use, as not all latest-version client libraries support v2.
    3. It would be useful to learn how the Socket.IO Protocol is structured. In short, events are prefixed with 2, acknowledgements are prefixed with 3 (both are after the Engine.IO prefixes/headers), and the data of the event is stored as a JSON-encoded array of items, where the first item is always a string identifying the event name, and optional subsequent items are the arguments to the event.
  3. Sails Socket Client
    1. The Sails socket client primarily adds HTTP request/response emulation on top of Socket.IO. For instance, it adds HTTP verbs such as GET, POST, etc. onto the socket connection, and uses Socket.IO's acknowledgements in order to send back a response body.
    2. This is implemented by emitting Socket.IO events where the event name is the HTTP verb (e.g. "get", "post", etc.), and the first and only argument to the Socket.IO event is a data structure with Sails-specific fields: method, headers, url, and data. The data field is where the application-specific data is stored in the event.
    3. The Sails response is sent back to the client as a Socket.IO acknowledgement in a similar format with the body, headers, and statusCode fields as the first and only argument in the ack.
    4. There is a reference Sails client library for JavaScript, but there is no other official reference implementation.
  4. Floatplane
    1. The final layer is the application Floatplane itself, which is described in this document.

Over-the-Wire Examples

The following list shows some examples of what a raw WebSocket connection might entail, and splits up the data between the various layers. This section is mostly to help with debugging raw connections, and understanding the technical stack of Floatplane.

  1. C->S GET wss://www.floatplane.com/socket.io/?__sails_io_sdk_version=0.13.8&__sails_io_sdk_platform=browser&__sails_io_sdk_language=javascript&EIO=3&transport=websocket
    1. The client first connects directly to the WebSocket endpoint, and receives an HTTP 101 Switching Protocols.
    2. This is Engine.IO only.
  2. S->C 0{"sid":"b1VcCLtZ1SUXiYEGAB49","upgrades":[],"pingInterval":25000,"pingTimeout":60000}
    1. The WebSocket connection is established and the server sends the open packet.
    2. 0: Engine.IO open header.
    3. {"sid":"b1VcCLtZ1SUXiYEGAB49","upgrades":[],"pingInterval":25000,"pingTimeout":60000}: Engine.IO open payload.
    4. Note that if using a proper Engine.IO or Socket.IO implementation, you will not need to see or handle this in your code.
    5. This is Engine.IO only.
  3. S->C 40
    1. The server automatically connects the client to the Socket.IO default namespace (/).
    2. 4: Engine.IO message header.
    3. 0: Socket.IO connect header.
    4. Note that the namespace / is implied but not included in this message due to the Socket.IO protocol marking is as optional. The non-encoded message for this in Socket.IO would look like {"type": 0,"nsp": "/admin","data": {}}.
    5. Note that if using a proper Socket.IO library implementation, you will not need to see or handle this in your code.
    6. This is Engine.IO and Socket.IO only.
  4. C->S: 420["post",{"method":"post","headers":{},"data":{},"url":"/api/v3/socket/connect"}]
    1. The client posts to the socket connect URL in Sails, in order for Floatplane to connect and listen for events.
    2. 4: Engine.IO message header.
    3. 20: Socket.IO header.
      1. 2: Marks that this message is an Event.
      2. 0: Id parameter. Needed for the following acknowledgement.
    4. ["post",{"method":"post","headers":{},"data":{},"url":"/api/v3/socket/connect"}]: Socket.IO event data.
      1. "post": Socket.IO event name.
        1. This event name is reserved and used by Sails.
      2. {"method":"post","headers":{},"data":{},"url":"/api/v3/socket/connect"}: Socket.IO event argument for the Sails "post" event.
        1. "method":"post,"url":"/api/v3/socket/connect": This Sails POST request is destined for the /api/v3/socket/connect endpoint.
        2. "data":{}: Sails request body.
          1. {}: No Floatplane data in the request.
  5. S->C: 430[{"body":{"message":"User sync setup."},"headers":{},"statusCode":200}]
    1. The Floatplane server responds to the POST request in Sails, telling the Floatplane client that it is connected.
    2. 4: Engine.IO message header.
    3. 30: Socket.IO header.
      1. 3: Marks that this message is an Acknowledgement
      2. 0: Id parameter. Marks that it is acknowledging event number 0.
    4. [{"body":{"message":"User sync setup."},"headers":{},"statusCode":200}]: Socket.IO acknowledgement data.
      1. {"body":{"message":"User sync setup."},"headers":{},"statusCode":200}: The only argument in the Socket.IO ack
        1. "statusCode":200: Sails HTTP response status code.
        2. "body":{"message":"User sync setup."}: Sails HTTP response body.
          1. {"message":"User sync setup."}: Floatplane connection response.
  6. S->C: 421["syncEvent",{"event": "creatorNotification","data": {"id": "CONTENT_POST_RELEASE:yQDi4v3EMc","eventType": "CONTENT_POST_RELEASE",...}}]
    1. The Floatplane server sends an event to the client about a new post notification.
    2. 4: Engine.IO message header.
    3. 2: Socket.IO event header.
    4. ["syncEvent",{"event": "creatorNotification","data": {"id": "CONTENT_POST_RELEASE:yQDi4v3EMc","eventType": "CONTENT_POST_RELEASE",...}}]: Socket.IO event data.
      1. "syncEvent": Socket.IO event name.
        1. This name is used by the Floatplane application. The server is using the Sails socket to emit the event, but does not add anything to the request. It is basically emitting directly on the socket from Socket.IO.
      2. {"event": "creatorNotification","data": {"id": "CONTENT_POST_RELEASE:yQDi4v3EMc","eventType": "CONTENT_POST_RELEASE",...}}: Socket.IO event argument for the "syncEvent" event.
        1. This is structured entirely by Floatplane.

Document Organization

This document is primarily organized around the AsyncAPI blog post on Socket.IO (Part 1, and Part 2). The extension x-ack is used in order to model Socket.IO's acknowledgement feature that emulates the request/response paradigm. The Socket.IO acknowledgement does not have an event name that goes over the wire, and instead directly references the initiating event via its unique identifier. As such, acknowledgements are not specified in the AsyncAPI's channel's publisher or subscriber operators.

This document also manually incorporates features of Sails on top of Socket.IO. It does so manually because the Sails abstraction is rather light, by basically specifying event names and models for data to pass through for requests and responses to virtual endpoints. Sails introduces the event names "get", "post", "put", and other HTTP verbs, where the event argument (request) is JSON in the form of:

{
    "method": "get",
    "headers": {},
    "body": {
        ...
    },
    "url": "/api/v3/..."
}

And the acknowledgement/response argument is JSON in the form of:

{
    "body": {
        ...
    },
    "headers": {},
    "statusCode: 200
}

Where body in each is specific to Floatplane. The rest of the data structure emulates HTTP requests and responses. As such, this AsyncAPI document explicitly models these structures around the Floatplane-specific requests and responses.

Finally, because Sails uses generic "get", "post", etc. event names for multiple types of actualized events on Socket.IO, a single AsyncAPI Operator is defined for each of "get" and "post", and uses JSON Schema's oneOf feature for multiple kinds of body models, one for each request/response.

Useful links for AsyncAPI and WebSockets/Socket.IO:

Socket.IO Connection Tips

When configuring a Socket.IO connection for use with Floatplane, there are some particular configurations to perform in order to prevent issues.

  • Path: /socket.io
    • Floatplane's preferred Socket.IO path is /socket.io.
    • By default, some client libraries will use a path of /engine.io and may result in an HTTP 404 if used.
  • Secure: true
    • Floatplane is HTTPS/WSS only, and has HTTP/WS disabled.
    • Some client libraries have TLS disabled by default.
  • Transports: websocket / Secure: true
    • Floatplane appears to have HTTP long-polling disabled with their Engine.IO configuration, and thus the only option available is to use WebSockets.
    • When connecting to Floatplane's sockets, client libraries typically try to default to HTTP and only upgrade to WebSockets afterward. This may not work correctly with Floatplane, so attempt to connect via WebSockets by default.
  • Query / ConnectParams
    • Set the query or connection parameters like so:
      • __sails_io_sdk_version: 0.13.8
      • __sails_io_sdk_platform: {your platform}
      • __sails_io_sdk_language: {your language}
    • These are required for Sails to initialize properly. Floatplane's Sails version defaults to the version to 0.9.0 which will throw an error when performing an Sails-related events, because it thinks your code will be too old to handle it.
  • Headers:
    • Origin: https://www.floatplane.com"
      • For security-related purposes, Floatplane will deny WebSocket connections from what it thinks are other websites. This is to prevent cross-site request forgery.
      • When implementing an application in a browser, this is not customizable. But from a regular application, this is needed in order for Floatplane to trust your connection.
    • Cookies:
      • Some client libraries in Socket.IO have a separate configuration for Cookies, while others require you to bundle it in the extraHeaders configuration.
      • sails.sid
        • The sails.sid cookie is not required to make a raw Socket.IO connection with Floatplane, but will be required for making most Sails get/post requests.
        • Otherwise, the socket connection is largely useless.

Servers

  • www.floatplane.com/socket.io/?__sails_io_sdk_version={sailsVersion}&__sails_io_sdk_platform={sailsPlatform}&__sails_io_sdk_language={sailsLanguage}&EIO=3&transport=websocketwsswebsite

    A client connects to Floatplane's asynchronous API via WebSockets with TLS (wss). The socket it connects to is goeverned by Sails, which requires specifying which version of Sails to use, as well as other parameters like the platform and language. The purpose of the EIO query parameter is unknown. If using a proper Sails client library, the parameters should be auto-filled for you, and simply connecting to www.floatplane.com/socket.io/ should suffice.

    sailsVersion
    required
    string

    The value 0.13.8 is the current value at the time of writing (2022-05-08). This gets updated every so often with updates to the Floatplane frontend. There may be compatibility issues if too old of a value is supplied (the Sails backend may reject the connection). It is important to keep this value as up-to-date as possible, in order to prevent rejected connection issues.

    Default value:"0.13.8"
      Examples values:
    • "0.13.8"
    sailsPlatform
    required
    string

    The value browser is the current value used by the Sails JS client library. It is not known what effect values other than those defined by Sails may have on the socket connection.

    Default value:"browser"
      Examples values:
    • "browser"
    • "node"
    sailsLanguage
    required
    string

    The value javascript is the current value used by the Sails JS client library. It is not known what effect values other than javascript may have on the socket connection.

    Default value:"javascript"
      Examples values:
    • "javascript"

Operations

  • PUB /

    Socket.IO and Sails groups messages and events into different namespaces, with / being the default namespace. Multiple kinds of messages can be sent in a single namespace, with Socket.IO having its own mechanisms to differentiate message types. Floatplane only uses the root Socket.IO namespace (/).

    Operation IDrootPublish

    Accepts the following message:

    Sails HTTP POSTpost

    HTTP POST via Sails socket connection

    Message IDpost

    This one asynchronous Sails/Socket.IO event may contain different Floatplane events in the payload, and contain different Floatplane events in the response/acknowledgement (depending on the request).

    oneOf

    Examples

  • SUB /

    Socket.IO and Sails groups messages and events into different namespaces, with / being the default namespace. Multiple kinds of messages can be sent in a single namespace, with Socket.IO having its own mechanisms to differentiate message types. Floatplane only uses the root Socket.IO namespace (/).

    Operation IDrootSubscribe

    Accepts one of the following messages:

    • #0Floatplane Sync EventsyncEvent

      A sync event arrives from the Floatplane server after registrating with the connect event (`/api/v3/socket/connect`).

      Message IDsyncEvent

      The sync event is another wrapper over Socket.IO, where the actual event type is embedded within the Socket.IO event payload. Most kinds of sync events are notifications of new blog posts on Floatplane from a subscribed creator.

      oneOf

      Examples

    • #1Poll OpenedpollOpen

      A `pollOpen` event arrives when a creator has started a new poll for users to vote on.

      Message IDpollOpen

      This provides options for the user to choose from, when the poll opened, and when it will automatically close. If the endDate is reached, the UI should show the conclusion and hide the poll as there will be no corresponding PollClose event. The startDate and endDate can be used to show a timer in the UI. In most polls, only a single option is permitted to be voted on, and the user is only allowed one chance at a vote.

      In order to vote on a poll, perform an HTTP POST to /api/v3/poll/vote. Look at the OpenAPI specification document for more information.

      object

      This schema is used for both PollOpen and PollClose.

      Examples

    • #2Poll ClosedpollClose

      A `pollClose` event arrives when a creator has ended a poll earlier than the expected `endDate`. Sometimes this happens right before starting a new poll, as it appears only one poll can be active at a time, per-creator. This payload will include the same details as the PollOpen event, but with an actual `endDate` as well as the final results of the tally.

      Message IDpollClose
      object

      This schema is used for both PollOpen and PollClose.

      Examples

    • #3Poll Tally UpdatedpollUpdateTally

      A `pollUpdateTally` even will arrive upon every vote on the poll by a user.

      Message IDpollUpdateTally

      The tick value will increment by one for each event, and one of the option counters should increment by one due to each vote.

      object

      Examples

Messages

  • #1Sails HTTP POSTpost

    HTTP POST via Sails socket connection

    Message IDpost

    This one asynchronous Sails/Socket.IO event may contain different Floatplane events in the payload, and contain different Floatplane events in the response/acknowledgement (depending on the request).

    oneOf
  • #2Floatplane Sync EventsyncEvent

    A sync event arrives from the Floatplane server after registrating with the connect event (`/api/v3/socket/connect`).

    Message IDsyncEvent

    The sync event is another wrapper over Socket.IO, where the actual event type is embedded within the Socket.IO event payload. Most kinds of sync events are notifications of new blog posts on Floatplane from a subscribed creator.

    oneOf
  • #3Poll OpenedpollOpen

    A `pollOpen` event arrives when a creator has started a new poll for users to vote on.

    Message IDpollOpen

    This provides options for the user to choose from, when the poll opened, and when it will automatically close. If the endDate is reached, the UI should show the conclusion and hide the poll as there will be no corresponding PollClose event. The startDate and endDate can be used to show a timer in the UI. In most polls, only a single option is permitted to be voted on, and the user is only allowed one chance at a vote.

    In order to vote on a poll, perform an HTTP POST to /api/v3/poll/vote. Look at the OpenAPI specification document for more information.

    object

    This schema is used for both PollOpen and PollClose.

  • #4Poll ClosedpollClose

    A `pollClose` event arrives when a creator has ended a poll earlier than the expected `endDate`. Sometimes this happens right before starting a new poll, as it appears only one poll can be active at a time, per-creator. This payload will include the same details as the PollOpen event, but with an actual `endDate` as well as the final results of the tally.

    Message IDpollClose
    object

    This schema is used for both PollOpen and PollClose.

  • #5Poll Tally UpdatedpollUpdateTally

    A `pollUpdateTally` even will arrive upon every vote on the poll by a user.

    Message IDpollUpdateTally

    The tick value will increment by one for each event, and one of the option counters should increment by one due to each vote.

    object