Source: components/client-factory.js

  1. "use strict";
  2. /**
  3. * @constructor
  4. * @param {Object} opts Options for this factory
  5. * @param {*=} opts.sdk The Matrix JS SDK require() to use.
  6. * @param {string=} opts.url The Client-Server base HTTP URL. This must be set
  7. * prior to calling getClientAs(). See configure() to set this after instantiation.
  8. * @param {string=} opts.token The application service token to use. This must
  9. * be set prior to calling getClientAs(). See configure() to set this after
  10. * instantiation.
  11. * @param {string=} opts.appServiceUserId The application service's user ID. Must
  12. * be set prior to calling getClientAs(). See configure() to set this after
  13. * instantiation.
  14. * @param {function=} opts.clientSchedulerBuilder Optional. A function that
  15. * returns a new client scheduler to use in place of the default event
  16. * scheduler that schedules events to be sent to the HS.
  17. */
  18. function ClientFactory(opts) {
  19. opts = opts || {};
  20. this._sdk = opts.sdk || require("matrix-js-sdk");
  21. this._clients = {
  22. // request_id: {
  23. // user_id: Client
  24. // }
  25. };
  26. this._clientSchedulerBuilder = opts.clientSchedulerBuilder || function() {};
  27. this.configure(opts.url, opts.token, opts.appServiceUserId);
  28. }
  29. /**
  30. * Set a function to be called when logging requests and responses.
  31. * @param {Function} func The function to invoke. The first arg is the string to
  32. * log. The second arg is a boolean which is 'true' if the log is an error.
  33. */
  34. ClientFactory.prototype.setLogFunction = function(func) {
  35. if (!func) {
  36. return;
  37. }
  38. this._sdk.wrapRequest(function(origRequest, opts, callback) {
  39. var logPrefix = (
  40. (opts._matrix_opts && opts._matrix_opts._reqId ?
  41. "[" + opts._matrix_opts._reqId + "] " : ""
  42. ) +
  43. opts.method + " " + opts.uri + " " +
  44. (opts.qs.user_id ? "(" + opts.qs.user_id + ")" : "(AS)")
  45. );
  46. // Request logging
  47. func(
  48. logPrefix + " Body: " +
  49. (opts.body ? JSON.stringify(opts.body).substring(0, 80) : "")
  50. );
  51. // Make the request
  52. origRequest(opts, function(err, response, body) {
  53. // Response logging
  54. var httpCode = response ? response.statusCode : null;
  55. var responsePrefix = logPrefix + " HTTP " + httpCode;
  56. if (err) {
  57. func(
  58. responsePrefix + " Error: " + JSON.stringify(err), true
  59. );
  60. }
  61. else if (httpCode >= 300 || httpCode < 200) {
  62. func(
  63. responsePrefix + " Error: " + JSON.stringify(body), true
  64. );
  65. }
  66. else {
  67. func( // body may be large, so do first 80 chars
  68. responsePrefix + " " +
  69. JSON.stringify(body).substring(0, 80)
  70. );
  71. }
  72. // Invoke the callback
  73. callback(err, response, body);
  74. });
  75. });
  76. };
  77. /**
  78. * Construct a new Matrix JS SDK Client. Calling this twice with the same args
  79. * will return the *same* client instance.
  80. * @param {?string} userId Required. The user_id to scope the client to. A new
  81. * client will be created per user ID. If this is null, a client scoped to the
  82. * application service *itself* will be created.
  83. * @param {Request=} request Optional. The request ID to additionally scope the
  84. * client to. If set, this will create a new client per user ID / request combo.
  85. * This factory will dispose the created client instance when the request is
  86. * resolved.
  87. */
  88. ClientFactory.prototype.getClientAs = function(userId, request) {
  89. var reqId = request ? request.getId() : "-";
  90. var userIdKey = userId || "bot";
  91. var self = this;
  92. // see if there is an existing match
  93. var client = this._getClient(reqId, userIdKey);
  94. if (client) {
  95. return client;
  96. }
  97. // create a new client
  98. var queryParams = {};
  99. if (userId) {
  100. queryParams.user_id = userId;
  101. }
  102. // force set access_token= so it is used when /register'ing
  103. queryParams.access_token = this._token;
  104. var clientOpts = {
  105. accessToken: this._token,
  106. baseUrl: this._url,
  107. userId: userId || this._botUserId, // NB: no clobber so we don't set ?user_id=BOT
  108. queryParams: queryParams,
  109. scheduler: this._clientSchedulerBuilder(),
  110. localTimeoutMs: 1000 * 60 * 2, // Time out CS-API calls after 2mins
  111. };
  112. client = this._sdk.createClient(clientOpts);
  113. client._http.opts._reqId = reqId; // FIXME gut wrenching
  114. // add a listener for the completion of this request so we can cleanup
  115. // the clients we've made
  116. if (request) {
  117. request.getPromise().finally(function() {
  118. delete self._clients[reqId];
  119. });
  120. }
  121. // store the new client
  122. if (!this._clients[reqId]) {
  123. this._clients[reqId] = {};
  124. }
  125. this._clients[reqId][userIdKey] = client;
  126. return client;
  127. };
  128. /**
  129. * Configure the factory for generating clients.
  130. * @param {string} baseUrl The base URL to create clients with.
  131. * @param {string} appServiceToken The AS token to use as the access_token
  132. * @param {string} appServiceUserId The AS's user_id
  133. */
  134. ClientFactory.prototype.configure = function(baseUrl, appServiceToken, appServiceUserId) {
  135. this._url = baseUrl;
  136. this._token = appServiceToken;
  137. this._botUserId = appServiceUserId;
  138. };
  139. ClientFactory.prototype._getClient = function(reqId, userId) {
  140. if (this._clients[reqId] && this._clients[reqId][userId]) {
  141. return this._clients[reqId][userId];
  142. }
  143. return null;
  144. };
  145. module.exports = ClientFactory;