Public sharing and invites

Public sharing

You can share CoValues publicly by setting the owner to a Group, and granting access to "everyone".

const group = Group.create();
group.addMember("everyone", "writer");

You can also use makePublic(role) alias to grant access to everyone with a specific role (defaults to reader).

const group = Group.create();
  group.addMember("everyone", "writer"); 
  group.makePublic("writer"); 
  // group.makePublic(); // Defaults to "reader" access

This is done in the chat example where anyone can join the chat, and send messages.

You can also add members by Account ID.

Invites

You can grant users access to a CoValue by sending them an invite link.

This is used in the todo example.

It generates a URL that looks like .../invite/[CoValue ID]/[inviteSecret]

In your app, you need to handle this route, and let the user accept the invitation, as done here.

You can accept an invitation programmatically by using the acceptInvite method on an account.

Pass the ID of the CoValue you're being invited to, the secret from the invite link, and the schema of the CoValue.

await account.acceptInvite(organizationId, inviteSecret, Organization);

Invite Secrets

The invite links generated by Jazz are convenient ways of handling invites.

In case you would prefer more direct control over the invite, you can create an invite to a Group using Group.createInvite(id, role) or group.$jazz.createInvite(role).

This will generate a string starting with inviteSecret_. You can then accept this invite using acceptInvite, with the group ID as the first argument, and the invite secret as the second.

const groupToInviteTo = Group.create();
const readerInvite = groupToInviteTo.$jazz.createInvite("reader");
// `inviteSecret_`

await account.acceptInvite(group.$jazz.id, readerInvite);
Security Note

Invites do not expire and cannot be revoked. If you choose to generate your own secrets in this way, take care that they are not shared in plain text over an insecure channel.

One particularly tempting mistake is passing the secret as a route parameter or a query. However, this will cause your secret to appear in server logs. You should only ever use fragment identifiers (i.e. parts after the hash in the URL) to share secrets, as these are not sent to the server (see the createInviteLink implementation).

Requesting Invites

To allow a non-group member to request an invitation to a group you can use the writeOnly role. This means that users only have write access to a specific requests list (they can't read other requests). However, Administrators can review and approve these requests.

Create the data models.

const JoinRequest = co.map({
  account: co.account(),
  status: z.literal(["pending", "approved", "rejected"]),
});

const RequestsList = co.list(JoinRequest);

Set up the request system with appropriate access controls.

function createRequestsToJoin() {
  const requestsGroup = Group.create();
  requestsGroup.addMember("everyone", "writeOnly");

  return RequestsList.create([], requestsGroup);
}

async function sendJoinRequest(
  requestsList: co.loaded<typeof RequestsList>,
  account: Account,
) {
  const request = JoinRequest.create(
    {
      account,
      status: "pending",
    },
    requestsList.$jazz.owner, // Inherit the access controls of the requestsList
  );

  requestsList.$jazz.push(request);

  return request;
}

Using the write-only access users can submit requests that only administrators can review and approve.

async function approveJoinRequest(
  joinRequest: co.loaded<typeof JoinRequest, { account: true }>,
  targetGroup: Group,
) {
  const account = await co.account().load(joinRequest.$jazz.refs.account.id);

  if (account.$isLoaded) {
    targetGroup.addMember(account, "reader");
    joinRequest.$jazz.set("status", "approved");

    return true;
  } else {
    return false;
  }
}