diff --git a/.gitignore b/.gitignore
index 69ebc2f..f8036f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,3 +74,4 @@ Makefile.in
/src/matrix-event-room-message-feedback.c
/src/matrix-event-room-guest-access.c
/src/matrix-event-room-redaction.c
+/src/matrix-event-room-third-party-invite.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 1d8dcb6..7df85f7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -44,6 +44,7 @@ libmatrix_glib_0_0_la_VALA_SOURCES = \
matrix-event-room-message-feedback.vala \
matrix-event-room-guest-access.vala \
matrix-event-room-redaction.vala \
+ matrix-event-room-third-party-invite.vala \
$(NULL)
AM_CPPFLAGS += \
diff --git a/src/matrix-event-room-third-party-invite.vala b/src/matrix-event-room-third-party-invite.vala
new file mode 100644
index 0000000..95f19f2
--- /dev/null
+++ b/src/matrix-event-room-third-party-invite.vala
@@ -0,0 +1,194 @@
+/*
+ * This file is part of matrix-glib-sdk
+ *
+ * matrix-glib-sdk is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * matrix-glib-sdk is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with matrix-glib-sdk. If not, see
+ * .
+ */
+
+/**
+ * Class to hold a m.room.third_party_invite event
+ *
+ * Acts as an m.room.member invite event, where there isn't a target
+ * user_id to invite. This event contains a token and a public key
+ * whose private key must be used to sign the token. Any user who can
+ * present that signature may use this invitation to join the target
+ * room.
+ */
+public class Matrix.Event.RoomThirdPartyInvite : Matrix.Event.State {
+ public struct PublicKey {
+ string? key;
+ string? validity_url;
+ }
+
+ private List _public_keys = null;
+
+ /**
+ * A user-readable string which represents the user who has been
+ * invited. This should not contain the user's third party ID, as
+ * otherwise when the invite is accepted it would leak the
+ * association between the matrix ID and the third party ID.
+ */
+ public string? display_name { get; set; default = null; }
+
+ /**
+ * A URL which can be fetched, with querystring
+ * public_key=public_key, to validate whether the key has been
+ * revoked. The URL must return a JSON object containing a boolean
+ * property named valid.
+ */
+ public string? key_validity_url { get; set; default = null; }
+
+ /**
+ * A base64-encoded ed25519 key with which token must be signed
+ * (though a signature from any entry in public_keys is also
+ * sufficient). This exists for backwards compatibility.
+ */
+ public string? public_key { get; set; default = null;}
+
+ /**
+ * Keys with which the token may be signed.
+ */
+ List? public_keys {
+ get {
+ return _public_keys;
+ }
+
+ set {
+ _public_keys = value.copy();
+ }
+
+ default = null;
+ }
+
+ /**
+ * The token, of which a signature must be produced in order to
+ * join the room.
+ */
+ public string? token {
+ get {
+ return _state_key;
+ }
+ set {
+ _state_key = value;
+ }
+
+ default = null;
+ }
+
+ protected override void
+ from_json(Json.Node json_data)
+ throws Matrix.Error
+ {
+ var root = json_data.get_object();
+ var content_root = root.get_member("content").get_object();
+ Json.Node? node;
+
+ if ((node = root.get_member("state_key")) != null) {
+ _state_key = node.get_string();
+ } else {
+ warning("state_key is missing from a m.room.third_party_invite_event");
+ }
+
+ if ((node = content_root.get_member("display_name")) != null) {
+ _display_name = node.get_string();
+ } else {
+ warning("content.display_name is missing from a m.room.third_party_invite event");
+ }
+
+ if ((node = content_root.get_member("public_key")) != null) {
+ _public_key = node.get_string();
+ } else {
+ warning("content.public_key is missing from a m.room.third_party_invite event");
+ }
+
+ if ((node = content_root.get_member("key_validity_url")) != null) {
+ _key_validity_url = node.get_string();
+ } else {
+ warning("content.key_validity_url is missing from a m.room.third_party_invite event");
+ }
+
+ if ((node = content_root.get_member("public_keys")) != null) {
+ }
+
+ base.from_json(json_data);
+ }
+
+ protected override void
+ to_json(Json.Node json_data)
+ throws Matrix.Error
+ {
+ if (_display_name == null) {
+ throw new Matrix.Error.INCOMPLETE(
+ "Won't generate a m.room.third_party_invite event without display_name");
+ }
+
+ if (_public_key == null) {
+ throw new Matrix.Error.INCOMPLETE(
+ "Won't generate a m.room.third_party_invite without public_key");
+ }
+
+ if (_key_validity_url == null) {
+ throw new Matrix.Error.INCOMPLETE(
+ "Won't generate a m.room.third_party_invite without key_validity_url");
+ }
+
+ if (_state_key == null) {
+ throw new Matrix.Error.INCOMPLETE(
+ "Won't generate a m.room.third_party_invite without token");
+ }
+
+ var content_root = json_data.get_object()
+ .get_member("content").get_object();
+
+ // We don't need to set the state key here, our base class
+ // will do it for us
+
+ content_root.set_string_member("display_name", _display_name);
+ content_root.set_string_member("public_key", _public_key);
+ content_root.set_string_member("key_validity_url", _key_validity_url);
+
+ var key_list = new Json.Array();
+
+ foreach (var entry in _public_keys) {
+ if (entry == null) {
+ throw new Matrix.Error.INCOMPLETE(
+ "Won't generate a m.room.third_party_invite with an empty public_key under additional keys");
+ }
+
+ if (entry.key == null) {
+ throw new Matrix.Error.INCOMPLETE(
+ "Won't generate a m.room.third_party_invite with a missing key under public_keys");
+ }
+
+ var key_obj = new Json.Object();
+ var key_node = new Json.Node(Json.NodeType.OBJECT);
+ key_node.set_object(key_obj);
+ key_obj.set_string_member("public_key", entry.key);
+
+ if (entry.validity_url != null) {
+ key_obj.set_string_member("key_validity_url", entry.validity_url);
+ }
+
+ key_list.add_element(key_node);
+ }
+
+ if (key_list.get_length() > 0) {
+ var keys_node = new Json.Node(Json.NodeType.ARRAY);
+ keys_node.set_array(key_list);
+ content_root.set_member("public_keys", keys_node);
+ }
+
+ base.to_json(json_data);
+ }
+}
diff --git a/src/matrix-event-types.c b/src/matrix-event-types.c
index 2373880..7b31a8a 100644
--- a/src/matrix-event-types.c
+++ b/src/matrix-event-types.c
@@ -206,6 +206,9 @@ matrix_event_types_ctor(void)
matrix_event_register_type("m.room.redaction",
MATRIX_EVENT_TYPE_ROOM_REDACTION,
NULL);
+ matrix_event_register_type("m.room.third_party_invite",
+ MATRIX_EVENT_TYPE_ROOM_THIRD_PARTY_INVITE,
+ NULL);
}
void