Skip to content

Commit 7e75090

Browse files
committed
Merge branch 'release/0.63.0'
2 parents 2ba5a66 + 7119490 commit 7e75090

File tree

10 files changed

+295
-61
lines changed

10 files changed

+295
-61
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,11 @@ There are a few ways you can overcome this problem:
167167
Solution number 1:
168168
You can create mutiple OAuth apps in Zoom's management dashboard, one for each instance of your app. This means that each instance will have their own clientId, clientSecret and accountId and therefore they can independently request tokens without interfering with each other.
169169

170-
This puts the onus is on you to create and manage these Zoom apps. Additionally, you are responsible for ensuring that the `OAuthConnectionInfo` in your C# code is initialized with the appropriate values for each instance.
171-
172-
This is a simple and effective solution when you have a relatively small number of instances but, in my opinion, it becomes overwhelming when your number of instances becomes too large.
170+
This puts the onus on you to create and manage these Zoom apps. Additionally, you are responsible for ensuring that the `OAuthConnectionInfo` in your C# code is initialized with the appropriate values for each instance. This is a simple and effective solution when you have a relatively small number of instances but, in my opinion, it becomes overwhelming when your number of instances becomes too large.
173171

174172

175173
Solution number 2:
176-
Create a single Zoom OAuth app. Contact Zoom support and request additional "token indices" (also known as "group numbers") for this OAuth app. Subsequently, new tokens can be "scoped" to a given index which means that a token issued for a specific index does not invalidate token for any other index. Hopefully, Zoom will grant you enough token indices and you will be able to dedicate one index for each instance of your application and you can subsequently modify your C# code to "scope"" you OAuth connection to a desired index, like so:
174+
Create a single Zoom OAuth app. Contact Zoom support and request additional "token indices" (also known as "group numbers") for this OAuth app. Subsequently, new tokens can be "scoped" to a given index which means that a token issued for a specific index does not invalidate token for any other index. Hopefully, Zoom will grant you enough token indices and you will be able to dedicate one index for each instance of your application and you can subsequently modify your C# code to "scope" you OAuth connection to a desired index, like so:
177175

178176
```csharp
179177
// you initialize the connection info for your first instance like this:
@@ -190,6 +188,14 @@ var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecre
190188

191189
Just like solution number 1, this solution works well for scenarios where you have a relatively small number of instances and where Zoom has granted you enough indices.
192190

191+
But what if you have more instances of your application than the number of indices that Zoom has granted you? For instance, what if you have 100 instances of your application running in the cloud (Azure, AWS, etc.) but Zoom granted you only 5 indices? If you are in this situation, the solutions I presented so far won't solve the problem. Keep reading, the next solution is a much better option for you.
192+
193+
194+
Solution number 3:
195+
You can make sure that all instances share the same token by storing the token information in a common repository that all your instances have access to. Examples of such repositories are: Azure blob storage, SQL server, Redis, MySQL, etc. For this solution to be effective, we must also make sure that your instances don't request new tokens at the same time because that would again tigger the problem described earlier where each new token invalidates the previous one.
196+
197+
If this solution sounds like a good option for your scenario, you're in luck: there's a beta version of ZoomNet that provides the necessary infrastructure and all you have to do is to write an implementation of a provided interface to provide the logic for the repository of your choice where token information information will be preserved and make accessible to all your application instances. ZoomNet takes care of allowing only one of your instances to be allowed to refresh a token at any given moment. If you are interrested in testing this beta version, leave a comment [here](https://github.com/Jericho/ZoomNet/issues/269).
198+
193199

194200
### Webhook Parser
195201

Source/ZoomNet.IntegrationTests/Tests/Meetings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie
5151
ApprovalType = ApprovalType.Manual,
5252
JoinBeforeHost = true,
5353
JoinBeforeHostTime = JoinBeforeHostTime.FiveMinutes,
54+
UsePmi = true,
5455
};
5556
var trackingFields = new Dictionary<string, string>()
5657
{
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.Json;
4+
5+
namespace ZoomNet.Json
6+
{
7+
/// <summary>
8+
/// Converts an array of <see cref="KeyValuePair{TKey, TValue}"/> to or from JSON.
9+
/// </summary>
10+
/// <seealso cref="ZoomNetJsonConverter{T}"/>
11+
internal class KeyValuePairConverter : ZoomNetJsonConverter<KeyValuePair<string, string>[]>
12+
{
13+
private const string DefaultKeyFieldName = "key";
14+
private const string DefaultValueFieldName = "value";
15+
16+
private readonly string _keyFieldName;
17+
private readonly string _valueFieldName;
18+
19+
public KeyValuePairConverter()
20+
: this(DefaultKeyFieldName, DefaultValueFieldName)
21+
{
22+
}
23+
24+
public KeyValuePairConverter(string keyFieldName, string valueFieldName)
25+
{
26+
_keyFieldName = keyFieldName;
27+
_valueFieldName = valueFieldName;
28+
}
29+
30+
public override KeyValuePair<string, string>[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
31+
{
32+
if (reader.TokenType == JsonTokenType.StartArray)
33+
{
34+
var values = new List<KeyValuePair<string, string>>();
35+
36+
reader.Read();
37+
38+
while ((reader.TokenType != JsonTokenType.EndArray) && reader.Read())
39+
{
40+
if (reader.TokenType == JsonTokenType.StartObject)
41+
{
42+
var fieldName = string.Empty;
43+
var fieldValue = string.Empty;
44+
45+
while ((reader.TokenType != JsonTokenType.EndObject) && reader.Read())
46+
{
47+
if (reader.TokenType == JsonTokenType.PropertyName)
48+
{
49+
var propertyName = reader.GetString();
50+
reader.Read();
51+
52+
if (propertyName == _keyFieldName) fieldName = reader.GetString();
53+
else if (propertyName == _valueFieldName) fieldValue = reader.GetString();
54+
}
55+
}
56+
57+
values.Add(new KeyValuePair<string, string>(fieldName, fieldValue));
58+
}
59+
}
60+
61+
return values.ToArray();
62+
}
63+
64+
throw new Exception("Unable to read Key/Value pair");
65+
}
66+
67+
public override void Write(Utf8JsonWriter writer, KeyValuePair<string, string>[] value, JsonSerializerOptions options)
68+
{
69+
if (value == null) return;
70+
71+
writer.WriteStartArray();
72+
73+
foreach (var item in value)
74+
{
75+
writer.WriteStartObject();
76+
writer.WriteString(_keyFieldName, item.Key);
77+
writer.WriteString(_valueFieldName, item.Value);
78+
writer.WriteEndObject();
79+
}
80+
81+
writer.WriteEndArray();
82+
}
83+
}
84+
}

Source/ZoomNet/Json/TrackingFieldsConverter.cs

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,16 @@ namespace ZoomNet.Json
1010
/// <seealso cref="ZoomNetJsonConverter{T}"/>
1111
internal class TrackingFieldsConverter : ZoomNetJsonConverter<KeyValuePair<string, string>[]>
1212
{
13+
private readonly KeyValuePairConverter _converter = new KeyValuePairConverter("field", "value");
14+
1315
public override KeyValuePair<string, string>[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1416
{
15-
if (reader.TokenType == JsonTokenType.StartArray)
16-
{
17-
var values = new List<KeyValuePair<string, string>>();
18-
19-
reader.Read();
20-
21-
while ((reader.TokenType != JsonTokenType.EndArray) && reader.Read())
22-
{
23-
if (reader.TokenType == JsonTokenType.StartObject)
24-
{
25-
var fieldName = string.Empty;
26-
var fieldValue = string.Empty;
27-
28-
while ((reader.TokenType != JsonTokenType.EndObject) && reader.Read())
29-
{
30-
if (reader.TokenType == JsonTokenType.PropertyName)
31-
{
32-
var propertyName = reader.GetString();
33-
reader.Read();
34-
35-
if (propertyName == "field") fieldName = reader.GetString();
36-
else if (propertyName == "value") fieldValue = reader.GetString();
37-
}
38-
}
39-
40-
values.Add(new KeyValuePair<string, string>(fieldName, fieldValue));
41-
}
42-
}
43-
44-
return values.ToArray();
45-
}
17+
return _converter.Read(ref reader, typeToConvert, options);
18+
}
4619

47-
throw new Exception("Unable to convert to tracking fields");
20+
public override void Write(Utf8JsonWriter writer, KeyValuePair<string, string>[] value, JsonSerializerOptions options)
21+
{
22+
_converter.Write(writer, value, options);
4823
}
4924
}
5025
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace ZoomNet.Models
2+
{
3+
/// <summary>
4+
/// Type of calendar integration used to schedule the meeting.
5+
/// </summary>
6+
public enum CalendarIntegrationType
7+
{
8+
/// <summary>
9+
/// <a href="https://support.zoom.us/hc/en-us/articles/360031592971-Getting-started-with-Outlook-plugin-and-add-in">Zoom Outlook add-in</a>.
10+
/// </summary>
11+
Outlook = 1,
12+
13+
/// <summary>
14+
/// <a href="https://support.zoom.us/hc/en-us/articles/360020187492-Using-the-Zoom-for-Google-Workspace-add-on">Zoom for Google Workspace add-on</a>.
15+
/// </summary>
16+
GoogleWorkspace = 2,
17+
}
18+
}

Source/ZoomNet/Models/Meeting.cs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Text.Json.Serialization;
4+
using ZoomNet.Json;
35

46
namespace ZoomNet.Models
57
{
@@ -24,12 +26,12 @@ public abstract class Meeting
2426
/// The id.
2527
/// </value>
2628
[JsonPropertyName("id")]
27-
28-
// This allows us to overcome the fact that "id" is sometimes a string and sometimes a number
29-
// See: https://devforum.zoom.us/t/the-data-type-of-meetingid-is-inconsistent-in-webhook-documentation/70090
30-
// Also, see: https://github.com/Jericho/ZoomNet/issues/228
29+
/*
30+
This allows us to overcome the fact that "id" is sometimes a string and sometimes a number
31+
See: https://devforum.zoom.us/t/the-data-type-of-meetingid-is-inconsistent-in-webhook-documentation/70090
32+
Also, see: https://github.com/Jericho/ZoomNet/issues/228
33+
*/
3134
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
32-
3335
public long Id { get; set; }
3436

3537
/// <summary>
@@ -126,5 +128,40 @@ public abstract class Meeting
126128
/// </summary>
127129
[JsonPropertyName("settings")]
128130
public MeetingSettings Settings { get; set; }
131+
132+
/// <summary>
133+
/// Gets or sets the timezone.
134+
/// For example, "America/Los_Angeles".
135+
/// Please reference our <a href="https://marketplace.zoom.us/docs/api-reference/other-references/abbreviation-lists#timezones">timezone list</a> for supported timezones and their formats.
136+
/// </summary>
137+
/// <value>The meeting timezone. For example, "America/Los_Angeles". Please reference our <a href="https://marketplace.zoom.us/docs/api-reference/other-references/abbreviation-lists#timezones">timezone list</a> for supported timezones and their formats.</value>
138+
[JsonPropertyName("timezone")]
139+
public string Timezone { get; set; }
140+
141+
/// <summary>
142+
/// Gets or sets the ID of the user who scheduled this meeting on behalf of the host.
143+
/// </summary>
144+
[JsonPropertyName("assistant_id")]
145+
public string AssistantId { get; set; }
146+
147+
/// <summary>
148+
/// Gets or sets the email address of the meeting host.
149+
/// </summary>
150+
[JsonPropertyName("host_email")]
151+
public string HostEmail { get; set; }
152+
153+
/// <summary>
154+
/// Gets or sets the encrypted passcode for third party endpoints (H323/SIP).
155+
/// </summary>
156+
[JsonPropertyName("encrypted_password")]
157+
public string EncryptedPassword { get; set; }
158+
159+
/// <summary>
160+
/// Gets or sets the tracking fields.
161+
/// </summary>
162+
/// <value>The tracking fields.</value>
163+
[JsonPropertyName("tracking_fields")]
164+
[JsonConverter(typeof(TrackingFieldsConverter))]
165+
public KeyValuePair<string, string>[] TrackingFields { get; set; } = Array.Empty<KeyValuePair<string, string>>();
129166
}
130167
}

Source/ZoomNet/Models/MeetingSettings.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using Newtonsoft.Json.Converters;
12
using System;
3+
using System.Collections.Generic;
24
using System.Text.Json.Serialization;
35

46
namespace ZoomNet.Models
@@ -156,5 +158,109 @@ public class MeetingSettings
156158
/// </summary>
157159
[JsonPropertyName("watermark")]
158160
public bool? Watermark { get; set; }
161+
162+
/// <summary>
163+
/// Gets or sets the value indicating whether to allow attendees to join the meeting from multiple devices.
164+
/// This setting only works for meetings that require registration.
165+
/// </summary>
166+
[JsonPropertyName("allow_multiple_devices")]
167+
public bool? AllowMultipleDevices { get; set; }
168+
169+
/// <summary>
170+
/// Gets or sets the value indicating whether to send email notifications to alternative hosts, default value is true.
171+
/// </summary>
172+
[JsonPropertyName("alternative_hosts_email_notification")]
173+
public bool? SendNotificationsToAlternativeHosts { get; set; }
174+
175+
/// <summary>
176+
/// Gets or sets the value indicating whether to allow alternative hosts to add or edit polls.
177+
/// This requires Zoom version 5.8.0 or higher.
178+
/// </summary>
179+
[JsonPropertyName("alternative_host_update_polls")]
180+
public bool? AllowAlternativeHostToUpdatePolls { get; set; }
181+
182+
/// <summary>
183+
/// Gets or sets the third party audio conference info.
184+
/// Constraints: Max 2048 chars.
185+
/// </summary>
186+
[JsonPropertyName("audio_conference_info")]
187+
public string ThirdPartyAudioConferenceInfo { get; set; }
188+
189+
/// <summary>
190+
/// Gets or sets the list the domains that are authenticated if user has configured "Sign Into Zoom with Specified Domains" option.
191+
/// </summary>
192+
[JsonPropertyName("authentication_domains")]
193+
public string AuthenticationDomains { get; set; }
194+
195+
/// <summary>
196+
/// Gets or sets the authentication name set in the authentication profile.
197+
/// </summary>
198+
[JsonPropertyName("authentication_name")]
199+
public string AuthenticationName { get; set; }
200+
201+
/// <summary>
202+
/// Gets or Sets the authentication option id.
203+
/// </summary>
204+
[JsonPropertyName("authentication_option")]
205+
public string AuthenticationOptionId { get; set; }
206+
207+
/// <summary>
208+
/// Gets or sets the type of calendar integration used to schedule the meeting.
209+
/// Works with the private_meeting field to determine whether to share details of meetings or not.
210+
/// </summary>
211+
[JsonPropertyName("calendar_type")]
212+
public CalendarIntegrationType? CalendarIntegrationType { get; set; }
213+
214+
/// <summary>
215+
/// Gets or sets the custom keys and values assigned to the meeting.
216+
/// </summary>
217+
[JsonPropertyName("custom_keys")]
218+
[JsonConverter(typeof(KeyValuePairConverter))]
219+
public KeyValuePair<string, string>[] CustomKeys { get; set; }
220+
221+
/// <summary>
222+
/// Gets or sets the value indicating whether to send email notifications to alternative hosts and users with <a href="https://support.zoom.us/hc/en-us/articles/201362803-Scheduling-privilege">scheduling privileges</a>.
223+
/// This value defaults to true.
224+
/// </summary>
225+
[JsonPropertyName("email_notification")]
226+
public bool? SendEmailNotifications { get; set; }
227+
228+
/// <summary>
229+
/// Gets or sets the encryption type.
230+
/// When using end-to-end encryption, several features (e.g. cloud recording, phone/SIP/H.323 dial-in) will be automatically disabled.
231+
/// </summary>
232+
[JsonPropertyName("encryption_type")]
233+
public EncryptionType? EncryptionType { get; set; }
234+
235+
/// <summary>
236+
/// Gets or sets the value indicating whether the Focus Mode feature is enabled when the meeting starts.
237+
/// </summary>
238+
[JsonPropertyName("focus_mode")]
239+
public bool? FocusModeEnabled { get; set; }
240+
241+
/// <summary>
242+
/// Gets or sets the value indicating whether only authenticated users can join meetings.
243+
/// </summary>
244+
[JsonPropertyName("meeting_authentication")]
245+
public bool? AuthenticationRequired { get; set; }
246+
247+
/// <summary>
248+
/// Gets or sets the value indicating whether the meeting is set as private.
249+
/// </summary>
250+
[JsonPropertyName("private_meeting")]
251+
public bool? Private { get; set; }
252+
253+
/// <summary>
254+
/// Gets or sets the value indicating whether to send registrants an email confirmation.
255+
/// </summary>
256+
[JsonPropertyName("registrants_email_notification")]
257+
public bool? SendConfirmationEmailToRegistrants { get; set; }
258+
259+
/// <summary>
260+
/// Gets or sets the value indicating whether to show social share buttons on the meeting registration page.
261+
/// This setting only works for meetings that require registration.
262+
/// </summary>
263+
[JsonPropertyName("show_share_button")]
264+
public bool? ShowShareButton { get; set; }
159265
}
160266
}

0 commit comments

Comments
 (0)