• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Jericho / ZoomNet / 834

15 Nov 2024 03:18PM UTC coverage: 20.194% (-0.2%) from 20.435%
834

push

appveyor

Jericho
Merge branch 'release/0.84.0'

645 of 3194 relevant lines covered (20.19%)

11.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.67
/Source/ZoomNet/ZoomClient.cs
1
using Microsoft.Extensions.Logging;
2
using Microsoft.Extensions.Logging.Abstractions;
3
using Pathoschild.Http.Client;
4
using Pathoschild.Http.Client.Extensibility;
5
using System;
6
using System.Collections.Generic;
7
using System.Linq;
8
using System.Net;
9
using System.Net.Http;
10
using System.Reflection;
11
using ZoomNet.Json;
12
using ZoomNet.Resources;
13
using ZoomNet.Utilities;
14

15
namespace ZoomNet
16
{
17
        /// <summary>
18
        /// REST client for interacting with Zoom's API.
19
        /// </summary>
20
        /// <remarks>
21
        /// Don't be fooled by the fact that this class implements the IDisposable interface: it is not meant to be short-lived and instantiated with every request.
22
        /// It is meant to be long-lived and re-used throughout the life of an application.
23
        /// The reason is: we use Microsoft's HttpClient to dispatch requests which itself is meant to be long-lived and re-used.
24
        /// Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads and will result in SocketException errors.
25
        ///
26
        /// See <a href="https://github.com/Jericho/ZoomNet/issues/35">this discussion</a> for more information about managing the lifetime of your client instance.
27
        /// </remarks>
28
        public class ZoomClient : IZoomClient, IDisposable
29
        {
30
                #region FIELDS
31

32
                private const string ZOOM_V2_BASE_URI = "https://api.zoom.us/v2";
33

34
                private static string _version;
35

36
                private readonly bool _mustDisposeHttpClient;
37
                private readonly ZoomClientOptions _options;
38
                private readonly ILogger _logger;
39

40
                private HttpClient _httpClient;
41
                private IClient _fluentClient;
42

43
                #endregion
44

45
                #region PROPERTIES
46

47
                /// <summary>
48
                /// Gets the Version.
49
                /// </summary>
50
                /// <value>
51
                /// The version.
52
                /// </value>
53
                public static string Version
54
                {
55
                        get
56
                        {
57
                                if (string.IsNullOrEmpty(_version))
4✔
58
                                {
59
                                        _version = typeof(ZoomClient).GetTypeInfo().Assembly.GetName().Version.ToString(3);
1✔
60
#if DEBUG
61
                                        _version = "DEBUG";
62
#endif
63
                                }
64

65
                                return _version;
4✔
66
                        }
67
                }
68

69
                /// <inheritdoc/>
70
                public IAccounts Accounts { get; private set; }
71

72
                /// <inheritdoc/>
73
                public ICallLogs CallLogs { get; private set; }
74

75
                /// <inheritdoc/>
76
                public IChat Chat { get; private set; }
77

78
                /// <inheritdoc/>
79
                public IChatbot Chatbot { get; private set; }
80

81
                /// <inheritdoc/>
82
                public ICloudRecordings CloudRecordings { get; private set; }
83

84
                /// <inheritdoc/>
85
                public IContacts Contacts { get; private set; }
86

87
                /// <inheritdoc/>
88
                public IDashboards Dashboards { get; private set; }
89

90
                /// <inheritdoc/>
91
                [Obsolete("The Data Compliance API is deprecated")]
92
                public IDataCompliance DataCompliance { get; private set; }
93

94
                /// <inheritdoc/>
95
                public IGroups Groups { get; private set; }
96

97
                /// <inheritdoc/>
98
                public IMeetings Meetings { get; private set; }
99

100
                /// <inheritdoc/>
101
                public IPastMeetings PastMeetings { get; private set; }
102

103
                /// <inheritdoc/>
104
                public IPastWebinars PastWebinars { get; private set; }
105

106
                /// <inheritdoc/>
107
                public IPhone Phone { get; private set; }
108

109
                /// <inheritdoc/>
110
                public IReports Reports { get; private set; }
111

112
                /// <inheritdoc/>
113
                public IRoles Roles { get; private set; }
114

115
                /// <inheritdoc/>
116
                public ISms Sms { get; private set; }
117

118
                /// <inheritdoc/>
119
                public IUsers Users { get; private set; }
120

121
                /// <inheritdoc/>
122
                public IWebinars Webinars { get; private set; }
123

124
                #endregion
125

126
                #region CTOR
127

128
                /// <summary>
129
                /// Initializes a new instance of the <see cref="ZoomClient"/> class.
130
                /// </summary>
131
                /// <param name="connectionInfo">Connection information.</param>
132
                /// <param name="options">Options for the Zoom client.</param>
133
                /// <param name="logger">Logger.</param>
134
                public ZoomClient(IConnectionInfo connectionInfo, ZoomClientOptions options = null, ILogger logger = null)
135
                        : this(connectionInfo, new HttpClient(), true, options, logger)
1✔
136
                {
137
                }
×
138

139
                /// <summary>
140
                /// Initializes a new instance of the <see cref="ZoomClient"/> class with a specific proxy.
141
                /// </summary>
142
                /// <param name="connectionInfo">Connection information.</param>
143
                /// <param name="proxy">Allows you to specify a proxy.</param>
144
                /// <param name="options">Options for the Zoom client.</param>
145
                /// <param name="logger">Logger.</param>
146
                public ZoomClient(IConnectionInfo connectionInfo, IWebProxy proxy, ZoomClientOptions options = null, ILogger logger = null)
147
                        : this(connectionInfo, new HttpClient(new HttpClientHandler { Proxy = proxy, UseProxy = proxy != null }), true, options, logger)
1✔
148
                {
149
                }
1✔
150

151
                /// <summary>
152
                /// Initializes a new instance of the <see cref="ZoomClient"/> class with a specific handler.
153
                /// </summary>
154
                /// <param name="connectionInfo">Connection information.</param>
155
                /// <param name="handler">TThe HTTP handler stack to use for sending requests.</param>
156
                /// <param name="options">Options for the Zoom client.</param>
157
                /// <param name="logger">Logger.</param>
158
                public ZoomClient(IConnectionInfo connectionInfo, HttpMessageHandler handler, ZoomClientOptions options = null, ILogger logger = null)
159
                        : this(connectionInfo, new HttpClient(handler), true, options, logger)
×
160
                {
161
                }
×
162

163
                /// <summary>
164
                /// Initializes a new instance of the <see cref="ZoomClient"/> class with a specific http client.
165
                /// </summary>
166
                /// <param name="connectionInfo">Connection information.</param>
167
                /// <param name="httpClient">Allows you to inject your own HttpClient. This is useful, for example, to setup the HtppClient with a proxy.</param>
168
                /// <param name="options">Options for the Zoom client.</param>
169
                /// <param name="logger">Logger.</param>
170
                public ZoomClient(IConnectionInfo connectionInfo, HttpClient httpClient, ZoomClientOptions options = null, ILogger logger = null)
171
                        : this(connectionInfo, httpClient, false, options, logger)
1✔
172
                {
173
                }
1✔
174

175
                private ZoomClient(IConnectionInfo connectionInfo, HttpClient httpClient, bool disposeClient, ZoomClientOptions options, ILogger logger = null)
176
                {
177
                        if (connectionInfo == null) throw new ArgumentNullException(nameof(connectionInfo));
3✔
178

179
                        _mustDisposeHttpClient = disposeClient;
3✔
180
                        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
3✔
181
                        _options = options ?? new();
3✔
182
                        _logger = logger ?? NullLogger.Instance;
3✔
183
                        _fluentClient = new FluentClient(new Uri(ZOOM_V2_BASE_URI), httpClient)
3✔
184
                                .SetUserAgent($"ZoomNet/{Version} (+https://github.com/Jericho/ZoomNet)");
3✔
185

186
                        _fluentClient.Filters.Remove<DefaultErrorFilter>();
3✔
187

188
                        // Remove all the built-in formatters and replace them with our custom JSON formatter
189
                        _fluentClient.Formatters.Clear();
3✔
190
                        _fluentClient.Formatters.Add(new JsonFormatter());
3✔
191

192
                        // Order is important: the token handler (either JWT or OAuth) must be first, followed by DiagnosticHandler and then by ErrorHandler.
193
                        if (connectionInfo is JwtConnectionInfo jwtConnectionInfo)
3✔
194
                        {
195
                                var tokenHandler = new JwtTokenHandler(jwtConnectionInfo);
2✔
196
                                _fluentClient.Filters.Add(tokenHandler);
1✔
197
                                _fluentClient.SetRequestCoordinator(new ZoomRetryCoordinator(new Http429RetryStrategy(), tokenHandler));
1✔
198
                        }
199
                        else if (connectionInfo is OAuthConnectionInfo oAuthConnectionInfo)
1✔
200
                        {
201
                                var tokenHandler = new OAuthTokenHandler(oAuthConnectionInfo, httpClient);
1✔
202
                                _fluentClient.Filters.Add(tokenHandler);
1✔
203
                                _fluentClient.SetRequestCoordinator(new ZoomRetryCoordinator(new Http429RetryStrategy(), tokenHandler));
1✔
204
                        }
205
                        else
206
                        {
207
                                throw new ZoomException($"{connectionInfo.GetType()} is an unknown connection type", null, null, null, null);
×
208
                        }
209

210
                        // The list of filters must be kept in sync with the filters in Utils.GetFluentClient in the unit testing project.
211
                        _fluentClient.Filters.Add(new DiagnosticHandler(_options.LogLevelSuccessfulCalls, _options.LogLevelFailedCalls, _logger));
2✔
212
                        _fluentClient.Filters.Add(new ZoomErrorHandler());
2✔
213

214
                        Accounts = new Accounts(_fluentClient);
2✔
215
                        CallLogs = new CallLogs(_fluentClient);
2✔
216
                        Chat = new Chat(_fluentClient);
2✔
217
                        Chatbot = new Chatbot(_fluentClient);
2✔
218
                        CloudRecordings = new CloudRecordings(_fluentClient);
2✔
219
                        Contacts = new Contacts(_fluentClient);
2✔
220
                        Dashboards = new Dashboards(_fluentClient);
2✔
221
                        DataCompliance = new DataCompliance(_fluentClient);
2✔
222
                        Groups = new Groups(_fluentClient);
2✔
223
                        Meetings = new Meetings(_fluentClient);
2✔
224
                        PastMeetings = new PastMeetings(_fluentClient);
2✔
225
                        PastWebinars = new PastWebinars(_fluentClient);
2✔
226
                        Phone = new Phone(_fluentClient);
2✔
227
                        Reports = new Reports(_fluentClient);
2✔
228
                        Roles = new Roles(_fluentClient);
2✔
229
                        Sms = new Sms(_fluentClient);
2✔
230
                        Users = new Users(_fluentClient);
2✔
231
                        Webinars = new Webinars(_fluentClient);
2✔
232
                }
2✔
233

234
                /// <summary>
235
                /// Finalizes an instance of the <see cref="ZoomClient"/> class.
236
                /// </summary>
237
                ~ZoomClient()
238
                {
239
                        // The object went out of scope and finalized is called.
240
                        // Call 'Dispose' to release unmanaged resources
241
                        // Managed resources will be released when GC runs the next time.
242
                        Dispose(false);
×
243
                }
×
244

245
                #endregion
246

247
                #region PUBLIC METHODS
248

249
                /// <inheritdoc/>
250
                public bool HasPermissions(IEnumerable<string> scopes)
251
                {
252
                        var tokenHandler = _fluentClient.Filters.OfType<OAuthTokenHandler>().SingleOrDefault();
×
253
                        if (tokenHandler == null) throw new Exception("The concept of scopes only applies when using an OAuth connection.");
×
254

255
                        // Ensure the token (and by extension the scopes) is not expired
256
                        tokenHandler.RefreshTokenIfNecessary(false);
×
257

258
                        // The list of scopes can be empty if a previously issued token was specified when the OAuthConnectionInfo was instantiated.
259
                        // I am not aware of any way to fetch the list of scopes which would enable me to populate the list of scopes in the OAuthConnectionInfo.
260
                        // Therefore in this scenario the only workaround I can think of is to force the token to be refreshed.
261
                        var oAuthConnectionInfo = (OAuthConnectionInfo)tokenHandler.ConnectionInfo;
×
262
                        if (oAuthConnectionInfo.Scopes == null) tokenHandler.RefreshTokenIfNecessary(true); // Force the token to be refreshed wich will have the side-effect of populating the '.Scopes'
×
263

264
                        var missingScopes = scopes.Except(((OAuthConnectionInfo)tokenHandler.ConnectionInfo).Scopes).ToArray();
×
265
                        return !missingScopes.Any();
×
266
                }
267

268
                /// <inheritdoc/>
269
                public void Dispose()
270
                {
271
                        // Call 'Dispose' to release resources
272
                        Dispose(true);
1✔
273

274
                        // Tell the GC that we have done the cleanup and there is nothing left for the Finalizer to do
275
                        GC.SuppressFinalize(this);
1✔
276
                }
1✔
277

278
                /// <summary>
279
                /// Releases unmanaged and - optionally - managed resources.
280
                /// </summary>
281
                /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
282
                protected virtual void Dispose(bool disposing)
283
                {
284
                        if (disposing)
1✔
285
                        {
286
                                ReleaseManagedResources();
1✔
287
                        }
288
                        else
289
                        {
290
                                // The object went out of scope and the Finalizer has been called.
291
                                // The GC will take care of releasing managed resources, therefore there is nothing to do here.
292
                        }
293

294
                        ReleaseUnmanagedResources();
1✔
295
                }
1✔
296

297
                #endregion
298

299
                #region PRIVATE METHODS
300

301
                private void ReleaseManagedResources()
302
                {
303
                        if (_fluentClient != null)
1✔
304
                        {
305
                                _fluentClient.Dispose();
1✔
306
                                _fluentClient = null;
1✔
307
                        }
308

309
                        if (_httpClient != null && _mustDisposeHttpClient)
1✔
310
                        {
311
                                _httpClient.Dispose();
1✔
312
                                _httpClient = null;
1✔
313
                        }
314
                }
1✔
315

316
                private void ReleaseUnmanagedResources()
317
                {
318
                        // We do not hold references to unmanaged resources
319
                }
1✔
320

321
                #endregion
322
        }
323
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc