decent-chat / decent

Open source messaging platform for the modern web
https://meta.decent.chat
GNU General Public License v3.0
21 stars 5 forks source link

API spec: Road to 1.0.0 #269

Closed bates64 closed 6 years ago

bates64 commented 6 years ago

I want to get the API spec stabilized now, even before @decent/server has caught up to it. We've already got @MegaApuTurkUltra and co considering a C++ server implementation, and I don't want people to have to rewrite loads of stuff just because of a silly, breaking sub-1.0.0 PR here.

After the spec is drafted we can release it in a branch as a prerelease - 1.0.0-preview - or something, then properly release it once @decent/server has caught up. Note that everything after this initial release must adhere to semver!


To-do list:

Work is going on in the 1.0.0 branch.

bates64 commented 6 years ago

This is done. What next, and can I have a review? @PullJosh @towerofnix @TheInitializer

towerofnix commented 6 years ago

@heyitsmeuralex Why are permissions a bitfield? Those are extremely a pain to work with and extend. Can't we just have permissions be an object whose values are true and false?

permissions: {
  manageServer: false,
  manageOthers: true,
  manageRoles: false,
  ...
}

Oh, also, having both manageOthers and manageUsers is a bit confusing in my opinion. Have updateUsers and deleteUsers.

Also super tiny code nit, the "Roles" dropdown should be "Endpoints" in the Roles section.

bates64 commented 6 years ago

@towerofnix:

Can't we just have permissions be an object whose values are true and false?

The larger permissions gets, the network/storage cost of requesting/storing things goes up and up and up and up exponential. They're flags, handle em like flags.

Almost every language has some kind of bitfield/enum type anyway -- even C! "A pain to work with" is not really an argument for JS, either, now - I'm working on a Discord.js-like decent.js library right now for us to use in the client (#259), which would turn the permissions bitfield into an object just like that.

Also, no, they're not hard. At all!

const permissions = // ...
const canManageServer = (permissions & MANAGE_SERVER) === MANAGE_SERVER

Oh, also, having both manageOthers and manageUsers is a bit confusing in my opinion. Have updateUsers and deleteUsers.

Will replace both with a single MANAGE_USERS.

towerofnix commented 6 years ago

@heyitsmeuralex Just to clarify, what happens if we want 33 types of permissions? And what happens if we decide we want to get rid of a particular permission? Say we get rid of the permission that was taking up bit 10 - does bit 10 become "dead", an obsolete bit that has no meaning but cannot be overwritten for the purpose of a new setting, because that could cause issues on old databases?

bates64 commented 6 years ago

Just to clarify, what happens if we want 33 types of permissions?

We make the int 64-bit. Minor change. If JavaScript had unsigned integers it'd be even better. Note: Discord uses a 53-bit bitfield for permissions.

cannot be overwritten for the purpose of a new setting, because that could cause issues on old databases?

For that we'd bump the major version if it came to it.

towerofnix commented 6 years ago

The larger permissions gets, the network/storage cost of requesting/storing things goes up and up and up and up exponential.

Also, how is the storage exponential? (Storage of all roles) = (Number of roles) * (Number of permissions). There's no exponent there.

Also also, do we want to support unset and "negative" permissions? For example, how could I make a "muted" role, which strictly disables users from sending messages or reacting, but still lets them read messages? And how could this realistically be implemented with a bitset? After all, there are now three states each permission can be in: set to true, set to false, unset. Bitsets obviously can't represent that; and objects do gracefully (unset permissions simply wouldn't exist on the permissions object!).

bates64 commented 6 years ago

Storing a hashmap >>>>>>>>>> a 64-bit integer.

how could I make a "muted" role, which strictly disables users from sending messages or reacting

Roles would work like Object.assign - [ 0x08, 0x40 ] get &ed left-to-right and produce 0x48. I understand what you mean there, and that's an interesting thought. I just don't think it's worth how huge requests would become if we start sending 64-key objects down the line.

towerofnix commented 6 years ago

Frankly, why do you care about hashmap size when we are sending thousands of messages?

bates64 commented 6 years ago

Size on the network.

towerofnix commented 6 years ago

Well yes, but clients are receiving thousands of messages too. It's incomparable to the forty-or-so bytes from some permissions. (This very message is greatly bigger than the stringified hash map for {sendMessages: false, addReactions: false}!)

bates64 commented 6 years ago

Eh, I still don't see enough benefits to constitute it. Discord (extreme webscale) gets by with a bitfield.

towerofnix commented 6 years ago

No need to deal with major version bumps, so (significantly!) better server-client compatibility?

bates64 commented 6 years ago

Actually, no, changing a permission's meaning wouldn't be a breaking change - what do clients use the permission for? They're entirely server-side.

To be nice we'd probably deprecate its usage in client libraries before we delet it though.


Will we ever even reach 64 separate permissions globally?

towerofnix commented 6 years ago

The client uses the permission system for the UI for updating role permissions, and for determining what the logged-in user can or can't do. (For example, the send-message input/button should be disabled when the user does not have the permission to send messages.)

bates64 commented 6 years ago

And that's if we somehow reach 64 separate global permissions. I'd call it a non-issue.

Plus, were we to actually get to that point, we could defer the PR changing the permission to the next major release, which will happen eventually.

towerofnix commented 6 years ago

Why impose the artificial limit of 64 maximum, and make it significantly harder for developers to understand (e.g. people who aren't super experienced with bitfields), in the first place? The benefits are moot, since they relate strictly to network/storage usage, and that is irrelevant when you take into account messages.

bates64 commented 6 years ago

...fine. You win :package: If you change the docs to reflect it

towerofnix commented 6 years ago

A related topic: what about role priority?

Suppose I have these three roles:

Can send messages? Can read messages?
"Everyone" role No No
"Chat" role Yes Yes
"Muted" role No (Unset)

How do these stack?

Suppose somebody has both the "chat" and "muted" roles, as well as the "everyone" role, since all users are inherently affected by that. Obviously, what we want is that the user can view but not send messages. But how is that computed?

We could say "if there is any occurrence of No, then revoke this permission". So users who have "Chat" have Yes to both, but users who have "Muted" have No to "send messages", so they can't send messages. But this doesn't work, because everyone already has a No for both permissions - from the "Everyone" role.

I'm pretty sure Discord handles this by using a role priority system: roles which are closer to the head of the list (index 0; the top of the <ul>) are considered to have a higher priority than roles below.

Suppose we order our roles so that "Muted" has a higher priority than "Chat", and both are higher priority than "Everyone". Thus the computed permissions are:

Starting from the most prioritized role, as soon as a permission that is not unset is encountered, that is used as the final computed permission.

Describing Discord's system this way does make it seem a little complicated, but I think it's relatively intuitive to use, and I can't immediately think another way to compute the same results.

bates64 commented 6 years ago

:+1:

Looks good. Can you: