@knocklabs/client 0.4.5-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/cjs/api.js +165 -0
  4. package/dist/cjs/api.js.map +1 -0
  5. package/dist/cjs/clients/feed/feed.js +557 -0
  6. package/dist/cjs/clients/feed/feed.js.map +1 -0
  7. package/dist/cjs/clients/feed/index.js +43 -0
  8. package/dist/cjs/clients/feed/index.js.map +1 -0
  9. package/dist/cjs/clients/feed/interfaces.js +2 -0
  10. package/dist/cjs/clients/feed/interfaces.js.map +1 -0
  11. package/dist/cjs/clients/feed/store.js +115 -0
  12. package/dist/cjs/clients/feed/store.js.map +1 -0
  13. package/dist/cjs/clients/feed/types.js +2 -0
  14. package/dist/cjs/clients/feed/types.js.map +1 -0
  15. package/dist/cjs/clients/feed/utils.js +31 -0
  16. package/dist/cjs/clients/feed/utils.js.map +1 -0
  17. package/dist/cjs/clients/preferences/index.js +406 -0
  18. package/dist/cjs/clients/preferences/index.js.map +1 -0
  19. package/dist/cjs/clients/preferences/interfaces.js +2 -0
  20. package/dist/cjs/clients/preferences/interfaces.js.map +1 -0
  21. package/dist/cjs/index.js +108 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/interfaces.js +2 -0
  24. package/dist/cjs/interfaces.js.map +1 -0
  25. package/dist/cjs/knock.js +86 -0
  26. package/dist/cjs/knock.js.map +1 -0
  27. package/dist/cjs/networkStatus.js +21 -0
  28. package/dist/cjs/networkStatus.js.map +1 -0
  29. package/dist/esm/api.js +119 -0
  30. package/dist/esm/api.js.map +1 -0
  31. package/dist/esm/clients/feed/feed.js +337 -0
  32. package/dist/esm/clients/feed/feed.js.map +1 -0
  33. package/dist/esm/clients/feed/index.js +20 -0
  34. package/dist/esm/clients/feed/index.js.map +1 -0
  35. package/dist/esm/clients/feed/interfaces.js +2 -0
  36. package/dist/esm/clients/feed/interfaces.js.map +1 -0
  37. package/dist/esm/clients/feed/store.js +91 -0
  38. package/dist/esm/clients/feed/store.js.map +1 -0
  39. package/dist/esm/clients/feed/types.js +2 -0
  40. package/dist/esm/clients/feed/types.js.map +1 -0
  41. package/dist/esm/clients/feed/utils.js +18 -0
  42. package/dist/esm/clients/feed/utils.js.map +1 -0
  43. package/dist/esm/clients/preferences/index.js +176 -0
  44. package/dist/esm/clients/preferences/index.js.map +1 -0
  45. package/dist/esm/clients/preferences/interfaces.js +2 -0
  46. package/dist/esm/clients/preferences/interfaces.js.map +1 -0
  47. package/dist/esm/index.js +10 -0
  48. package/dist/esm/index.js.map +1 -0
  49. package/dist/esm/interfaces.js +2 -0
  50. package/dist/esm/interfaces.js.map +1 -0
  51. package/dist/esm/knock.js +69 -0
  52. package/dist/esm/knock.js.map +1 -0
  53. package/dist/esm/networkStatus.js +13 -0
  54. package/dist/esm/networkStatus.js.map +1 -0
  55. package/dist/types/api.d.ts +28 -0
  56. package/dist/types/api.d.ts.map +1 -0
  57. package/dist/types/clients/feed/feed.d.ts +46 -0
  58. package/dist/types/clients/feed/feed.d.ts.map +1 -0
  59. package/dist/types/clients/feed/index.d.ts +11 -0
  60. package/dist/types/clients/feed/index.d.ts.map +1 -0
  61. package/dist/types/clients/feed/interfaces.d.ts +50 -0
  62. package/dist/types/clients/feed/interfaces.d.ts.map +1 -0
  63. package/dist/types/clients/feed/store.d.ts +3 -0
  64. package/dist/types/clients/feed/store.d.ts.map +1 -0
  65. package/dist/types/clients/feed/types.d.ts +25 -0
  66. package/dist/types/clients/feed/types.d.ts.map +1 -0
  67. package/dist/types/clients/feed/utils.d.ts +4 -0
  68. package/dist/types/clients/feed/utils.d.ts.map +1 -0
  69. package/dist/types/clients/preferences/index.d.ts +18 -0
  70. package/dist/types/clients/preferences/index.d.ts.map +1 -0
  71. package/dist/types/clients/preferences/interfaces.d.ts +26 -0
  72. package/dist/types/clients/preferences/interfaces.d.ts.map +1 -0
  73. package/dist/types/index.d.ts +10 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/interfaces.d.ts +27 -0
  76. package/dist/types/interfaces.d.ts.map +1 -0
  77. package/dist/types/knock.d.ts +19 -0
  78. package/dist/types/knock.d.ts.map +1 -0
  79. package/dist/types/networkStatus.d.ts +8 -0
  80. package/dist/types/networkStatus.d.ts.map +1 -0
  81. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Knock Labs, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Knock Javascript client library
2
+
3
+ A client-side Javascript library to interact with user-facing Knock features, such as feeds.
4
+
5
+ **Note: this is a lower level library designed for building UI on top of**
6
+
7
+ ## Documentation
8
+
9
+ See the [documentation](https://docs.knock.app/notification-feeds/bring-your-own-ui) for usage examples.
10
+
11
+ ## Installation
12
+
13
+ Via NPM:
14
+
15
+ ```bash
16
+ npm install @knocklabs/client
17
+ ```
18
+
19
+ Via Yarn:
20
+
21
+ ```bash
22
+ yarn add @knocklabs/client
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ To configure the client library you will need:
28
+
29
+ 1. A public API key (found in the Knock dashboard)
30
+ 2. A feed channel ID (found in the Knock dashboard)
31
+ 3. A user ID, and optionally an auth token for production environments
32
+
33
+ ```typescript
34
+ import Knock from "@knocklabs/client";
35
+
36
+ const knockClient = new Knock(process.env.KNOCK_API_KEY);
37
+
38
+ knockClient.authenticate(
39
+ // The id of the user you want to authenticate against
40
+ currentUser.id,
41
+ // You only need this in production environments
42
+ currentUser.knockToken,
43
+ );
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ You can find an example usage in a React application in the [example/App.js](https://github.com/knocklabs/client-js/blob/main/example/src/App.js) file, which is a plain-old Create React App.
49
+
50
+ ### Retrieving new items from the feed
51
+
52
+ ```typescript
53
+ import Knock from "@knocklabs/client";
54
+
55
+ const knockClient = new Knock(process.env.KNOCK_API_KEY);
56
+
57
+ // Authenticate the user
58
+ knockClient.authenticate(currentUser.id, currentUser.knockToken);
59
+
60
+ // Initialize the feed
61
+ const feedClient = knockClient.feeds.initialize(
62
+ process.env.KNOCK_FEED_CHANNEL_ID,
63
+ // Optionally you can provide a default scope here:
64
+ // { tenant: "jurassic-park", source: "new-comment" },
65
+ );
66
+
67
+ // Connect to the real-time socket
68
+ const teardown = feedClient.listenForUpdates();
69
+
70
+ // Setup a callback for when a batch of messages is received (including on first load)
71
+ feedClient.on("messages.new", ({ entries }) => {
72
+ console.log(entries);
73
+ });
74
+
75
+ // Fetch the feed items
76
+ feedClient.fetch({
77
+ // Fetch a particular status only (defaults to all)
78
+ status: "all" | "unread" | "unseen",
79
+ // Pagination options
80
+ after: lastItem.__cursor,
81
+ before: firstItem.__cursor,
82
+ // Defaults to 50
83
+ page_size: 10,
84
+ // Filter by a specific source
85
+ source: "notification-key",
86
+ // Filter by a specific tenant
87
+ tenant: "jurassic-park",
88
+ });
89
+
90
+ teardown();
91
+ ```
92
+
93
+ ### Reading the feed store state (programmatically)
94
+
95
+ ```typescript
96
+ // Initialize the feed as in above examples
97
+ const feedClient = knockClient.feeds.initialize(
98
+ process.env.KNOCK_FEED_CHANNEL_ID,
99
+ );
100
+
101
+ // Gives you all of the items currently in the store
102
+ const { items } = feedClient.store.getState();
103
+ ```
104
+
105
+ ### Reading the feed store state (in React)
106
+
107
+ ```typescript
108
+ // The feed store uses zustand
109
+ import create from "zustand";
110
+
111
+ // Initialize the feed as in above examples
112
+ const feedClient = knockClient.feeds.initialize(
113
+ process.env.KNOCK_FEED_CHANNEL_ID,
114
+ );
115
+
116
+ const useFeedStore = create(feedClient.store);
117
+
118
+ // Retrieves all of the items
119
+ const items = useFeedStore((state) => state.items);
120
+
121
+ // Retrieve the badge counts
122
+ const meta = useFeedStore((state) => state.metadata);
123
+ ```
124
+
125
+ ### Marking items as read, seen, or archived
126
+
127
+ ```typescript
128
+ // Initialize the feed as in above examples
129
+ const feedClient = knockClient.feeds.initialize(
130
+ process.env.KNOCK_FEED_CHANNEL_ID,
131
+ );
132
+
133
+ // Mark one or more items as read
134
+ feedClient.markAsRead(feedItemOrItems);
135
+ // Mark one or more items as seen
136
+ feedClient.markAsSeen(feedItemOrItems);
137
+ // Mark one or more items as archived
138
+ feedClient.markAsArchived(feedItemOrItems);
139
+
140
+ // Mark one or more items as unread
141
+ feedClient.markAsUnread(feedItemOrItems);
142
+ // Mark one or more items as unseen
143
+ feedClient.markAsUnseen(feedItemOrItems);
144
+ // Mark one or more items as unarchived
145
+ feedClient.markAsUnarchived(feedItemOrItems);
146
+ ```
147
+
148
+ ### Managing user preferences
149
+
150
+ ```typescript
151
+ // Set an entire preference set
152
+ await knockClient.preferences.set({
153
+ channel_types: { email: true, sms: false },
154
+ workflows: {
155
+ "dinosaurs-loose": {
156
+ channel_types: { email: false, in_app_feed: true },
157
+ },
158
+ },
159
+ });
160
+
161
+ // Retrieve a whole preference set
162
+ const preferences = await knockClient.preferences.get();
163
+
164
+ // Granular preference setting for channel types
165
+ await knockClient.preferences.setChannelType("email", false);
166
+
167
+ // Granular preference setting for workflows
168
+ await knockClient.preferences.setWorkflow("dinosaurs-loose", {
169
+ channel_types: {
170
+ email: true,
171
+ in_app_feed: false,
172
+ },
173
+ });
174
+ ```
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports["default"] = void 0;
9
+
10
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
+
12
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
+
14
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
15
+
16
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
17
+
18
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
19
+
20
+ var _axios = _interopRequireDefault(require("axios"));
21
+
22
+ var _axiosRetry = _interopRequireDefault(require("axios-retry"));
23
+
24
+ var _phoenix = require("phoenix");
25
+
26
+ var ApiClient = /*#__PURE__*/function () {
27
+ function ApiClient(options) {
28
+ (0, _classCallCheck2["default"])(this, ApiClient);
29
+ (0, _defineProperty2["default"])(this, "host", void 0);
30
+ (0, _defineProperty2["default"])(this, "apiKey", void 0);
31
+ (0, _defineProperty2["default"])(this, "userToken", void 0);
32
+ (0, _defineProperty2["default"])(this, "axiosClient", void 0);
33
+ (0, _defineProperty2["default"])(this, "socket", void 0);
34
+ (0, _defineProperty2["default"])(this, "socketConnected", false);
35
+ this.host = options.host;
36
+ this.apiKey = options.apiKey;
37
+ this.userToken = options.userToken || null; // Create a retryable axios client
38
+
39
+ this.axiosClient = _axios["default"].create({
40
+ baseURL: this.host,
41
+ headers: {
42
+ Accept: "application/json",
43
+ "Content-Type": "application/json",
44
+ Authorization: "Bearer ".concat(this.apiKey),
45
+ "X-Knock-User-Token": this.userToken
46
+ }
47
+ });
48
+ this.socket = new _phoenix.Socket("".concat(this.host.replace("http", "ws"), "/ws/v1"), {
49
+ params: {
50
+ user_token: this.userToken,
51
+ api_key: this.apiKey
52
+ }
53
+ });
54
+ (0, _axiosRetry["default"])(this.axiosClient, {
55
+ retries: 3,
56
+ retryCondition: this.canRetryRequest,
57
+ retryDelay: _axiosRetry["default"].exponentialDelay
58
+ });
59
+ }
60
+
61
+ (0, _createClass2["default"])(ApiClient, [{
62
+ key: "connectSocket",
63
+ value: function connectSocket() {
64
+ var _this = this;
65
+
66
+ if (this.socketConnected) {
67
+ return;
68
+ }
69
+
70
+ this.socket.connect();
71
+ this.socket.onOpen(function () {
72
+ _this.socketConnected = true;
73
+ });
74
+ }
75
+ }, {
76
+ key: "disconnectSocket",
77
+ value: function disconnectSocket() {
78
+ this.socket.disconnect();
79
+ return;
80
+ }
81
+ }, {
82
+ key: "createChannel",
83
+ value: function createChannel(name, params) {
84
+ return this.socket.channel(name, params);
85
+ }
86
+ }, {
87
+ key: "makeRequest",
88
+ value: function () {
89
+ var _makeRequest = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(req) {
90
+ var result;
91
+ return _regenerator["default"].wrap(function _callee$(_context) {
92
+ while (1) {
93
+ switch (_context.prev = _context.next) {
94
+ case 0:
95
+ _context.prev = 0;
96
+ _context.next = 3;
97
+ return this.axiosClient(req);
98
+
99
+ case 3:
100
+ result = _context.sent;
101
+ return _context.abrupt("return", {
102
+ statusCode: result.status < 300 ? "ok" : "error",
103
+ body: result.data,
104
+ error: undefined,
105
+ status: result.status
106
+ });
107
+
108
+ case 7:
109
+ _context.prev = 7;
110
+ _context.t0 = _context["catch"](0);
111
+ // tslint:disable-next-line
112
+ console.error(_context.t0);
113
+ return _context.abrupt("return", {
114
+ statusCode: "error",
115
+ status: 500,
116
+ body: undefined,
117
+ error: _context.t0
118
+ });
119
+
120
+ case 11:
121
+ case "end":
122
+ return _context.stop();
123
+ }
124
+ }
125
+ }, _callee, this, [[0, 7]]);
126
+ }));
127
+
128
+ function makeRequest(_x) {
129
+ return _makeRequest.apply(this, arguments);
130
+ }
131
+
132
+ return makeRequest;
133
+ }()
134
+ }, {
135
+ key: "canRetryRequest",
136
+ value: function canRetryRequest(error) {
137
+ // Retry Network Errors.
138
+ if (_axiosRetry["default"].isNetworkError(error)) {
139
+ return true;
140
+ }
141
+
142
+ if (!error.response) {
143
+ // Cannot determine if the request can be retried
144
+ return false;
145
+ } // Retry Server Errors (5xx).
146
+
147
+
148
+ if (error.response.status >= 500 && error.response.status <= 599) {
149
+ return true;
150
+ } // Retry if rate limited.
151
+
152
+
153
+ if (error.response.status === 429) {
154
+ return true;
155
+ }
156
+
157
+ return false;
158
+ }
159
+ }]);
160
+ return ApiClient;
161
+ }();
162
+
163
+ var _default = ApiClient;
164
+ exports["default"] = _default;
165
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api.ts"],"names":["ApiClient","options","host","apiKey","userToken","axiosClient","axios","create","baseURL","headers","Accept","Authorization","socket","Socket","replace","params","user_token","api_key","retries","retryCondition","canRetryRequest","retryDelay","axiosRetry","exponentialDelay","socketConnected","connect","onOpen","disconnect","name","channel","req","result","statusCode","status","body","data","error","undefined","console","isNetworkError","response"],"mappings":"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","sourcesContent":["import axios, { AxiosInstance, AxiosRequestConfig } from \"axios\";\nimport axiosRetry from \"axios-retry\";\nimport { Socket } from \"phoenix\";\nimport { AxiosError } from \"axios\";\n\ntype ApiClientOptions = {\n host: string;\n apiKey: string;\n userToken: string | undefined;\n};\n\nexport interface ApiResponse {\n error?: any;\n body?: any;\n statusCode: \"ok\" | \"error\";\n status: number;\n}\n\nclass ApiClient {\n private host: string;\n private apiKey: string;\n private userToken: string | null;\n private axiosClient: AxiosInstance;\n private socket: Socket;\n public socketConnected: boolean = false;\n\n constructor(options: ApiClientOptions) {\n this.host = options.host;\n this.apiKey = options.apiKey;\n this.userToken = options.userToken || null;\n\n // Create a retryable axios client\n this.axiosClient = axios.create({\n baseURL: this.host,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"X-Knock-User-Token\": this.userToken,\n },\n });\n\n this.socket = new Socket(`${this.host.replace(\"http\", \"ws\")}/ws/v1`, {\n params: {\n user_token: this.userToken,\n api_key: this.apiKey,\n },\n });\n\n axiosRetry(this.axiosClient, {\n retries: 3,\n retryCondition: this.canRetryRequest,\n retryDelay: axiosRetry.exponentialDelay,\n });\n }\n\n connectSocket() {\n if (this.socketConnected) {\n return;\n }\n\n this.socket.connect();\n this.socket.onOpen(() => {\n this.socketConnected = true;\n });\n }\n\n disconnectSocket() {\n this.socket.disconnect();\n return;\n }\n\n createChannel(name: string, params?: object) {\n return this.socket.channel(name, params);\n }\n\n async makeRequest(req: AxiosRequestConfig): Promise<ApiResponse> {\n try {\n const result = await this.axiosClient(req);\n\n return {\n statusCode: result.status < 300 ? \"ok\" : \"error\",\n body: result.data,\n error: undefined,\n status: result.status,\n };\n } catch (e) {\n // tslint:disable-next-line\n console.error(e);\n\n return {\n statusCode: \"error\",\n status: 500,\n body: undefined,\n error: e,\n };\n }\n }\n\n private canRetryRequest(error: AxiosError) {\n // Retry Network Errors.\n if (axiosRetry.isNetworkError(error)) {\n return true;\n }\n\n if (!error.response) {\n // Cannot determine if the request can be retried\n return false;\n }\n\n // Retry Server Errors (5xx).\n if (error.response.status >= 500 && error.response.status <= 599) {\n return true;\n }\n\n // Retry if rate limited.\n if (error.response.status === 429) {\n return true;\n }\n\n return false;\n }\n}\n\nexport default ApiClient;\n"],"file":"api.js"}
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports["default"] = void 0;
9
+
10
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
+
12
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
+
14
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
15
+
16
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
17
+
18
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
19
+
20
+ var _eventemitter = _interopRequireDefault(require("eventemitter3"));
21
+
22
+ var _store = _interopRequireDefault(require("./store"));
23
+
24
+ var _networkStatus = require("../../networkStatus");
25
+
26
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
27
+
28
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
29
+
30
+ var Feed = /*#__PURE__*/function () {
31
+ // The raw store instance, used for binding in React and other environments
32
+ function Feed(knock, feedId, options) {
33
+ (0, _classCallCheck2["default"])(this, Feed);
34
+ this.knock = knock;
35
+ this.feedId = feedId;
36
+ (0, _defineProperty2["default"])(this, "apiClient", void 0);
37
+ (0, _defineProperty2["default"])(this, "userFeedId", void 0);
38
+ (0, _defineProperty2["default"])(this, "channelConnected", false);
39
+ (0, _defineProperty2["default"])(this, "channel", void 0);
40
+ (0, _defineProperty2["default"])(this, "broadcaster", void 0);
41
+ (0, _defineProperty2["default"])(this, "defaultOptions", void 0);
42
+ (0, _defineProperty2["default"])(this, "store", void 0);
43
+ this.apiClient = knock.client();
44
+ this.feedId = feedId;
45
+ this.userFeedId = this.buildUserFeedId();
46
+ this.store = (0, _store["default"])();
47
+ this.broadcaster = new _eventemitter["default"]();
48
+ this.defaultOptions = options; // Try and connect to the socket
49
+
50
+ this.apiClient.connectSocket();
51
+ this.channel = this.apiClient.createChannel("feeds:".concat(this.userFeedId), this.defaultOptions);
52
+ }
53
+ /*
54
+ Returns a socket to listen for feed updates
55
+ */
56
+
57
+
58
+ (0, _createClass2["default"])(Feed, [{
59
+ key: "listenForUpdates",
60
+ value: function listenForUpdates() {
61
+ var _this = this;
62
+
63
+ if (!this.channelConnected) {
64
+ this.channel.join().receive("ok", function () {
65
+ _this.channelConnected = true;
66
+ });
67
+ this.channel.on("new-message", function (resp) {
68
+ return _this.onNewMessageReceived(resp);
69
+ });
70
+ }
71
+
72
+ return function () {
73
+ try {
74
+ _this.channel.leave();
75
+ } catch (e) {
76
+ // tslint:disable-next-line
77
+ console.error("error while leaving channel", e);
78
+ }
79
+ };
80
+ }
81
+ /* Binds a handler to be invoked when event occurs */
82
+
83
+ }, {
84
+ key: "on",
85
+ value: function on(eventName, callback) {
86
+ this.broadcaster.on(eventName, callback);
87
+ }
88
+ }, {
89
+ key: "off",
90
+ value: function off(eventName, callback) {
91
+ this.broadcaster.off(eventName, callback);
92
+ }
93
+ }, {
94
+ key: "getState",
95
+ value: function getState() {
96
+ return this.store.getState();
97
+ }
98
+ }, {
99
+ key: "markAsSeen",
100
+ value: function () {
101
+ var _markAsSeen = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(itemOrItems) {
102
+ var now;
103
+ return _regenerator["default"].wrap(function _callee$(_context) {
104
+ while (1) {
105
+ switch (_context.prev = _context.next) {
106
+ case 0:
107
+ now = new Date().toISOString();
108
+ this.optimisticallyPerformStatusUpdate(itemOrItems, "seen", {
109
+ seen_at: now
110
+ }, "unseen_count");
111
+ return _context.abrupt("return", this.makeStatusUpdate(itemOrItems, "seen"));
112
+
113
+ case 3:
114
+ case "end":
115
+ return _context.stop();
116
+ }
117
+ }
118
+ }, _callee, this);
119
+ }));
120
+
121
+ function markAsSeen(_x) {
122
+ return _markAsSeen.apply(this, arguments);
123
+ }
124
+
125
+ return markAsSeen;
126
+ }()
127
+ }, {
128
+ key: "markAsUnseen",
129
+ value: function () {
130
+ var _markAsUnseen = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(itemOrItems) {
131
+ return _regenerator["default"].wrap(function _callee2$(_context2) {
132
+ while (1) {
133
+ switch (_context2.prev = _context2.next) {
134
+ case 0:
135
+ this.optimisticallyPerformStatusUpdate(itemOrItems, "unseen", {
136
+ seen_at: null
137
+ }, "unseen_count");
138
+ return _context2.abrupt("return", this.makeStatusUpdate(itemOrItems, "unseen"));
139
+
140
+ case 2:
141
+ case "end":
142
+ return _context2.stop();
143
+ }
144
+ }
145
+ }, _callee2, this);
146
+ }));
147
+
148
+ function markAsUnseen(_x2) {
149
+ return _markAsUnseen.apply(this, arguments);
150
+ }
151
+
152
+ return markAsUnseen;
153
+ }()
154
+ }, {
155
+ key: "markAsRead",
156
+ value: function () {
157
+ var _markAsRead = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(itemOrItems) {
158
+ var now;
159
+ return _regenerator["default"].wrap(function _callee3$(_context3) {
160
+ while (1) {
161
+ switch (_context3.prev = _context3.next) {
162
+ case 0:
163
+ now = new Date().toISOString();
164
+ this.optimisticallyPerformStatusUpdate(itemOrItems, "read", {
165
+ read_at: now
166
+ }, "unread_count");
167
+ return _context3.abrupt("return", this.makeStatusUpdate(itemOrItems, "read"));
168
+
169
+ case 3:
170
+ case "end":
171
+ return _context3.stop();
172
+ }
173
+ }
174
+ }, _callee3, this);
175
+ }));
176
+
177
+ function markAsRead(_x3) {
178
+ return _markAsRead.apply(this, arguments);
179
+ }
180
+
181
+ return markAsRead;
182
+ }()
183
+ }, {
184
+ key: "markAsUnread",
185
+ value: function () {
186
+ var _markAsUnread = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(itemOrItems) {
187
+ return _regenerator["default"].wrap(function _callee4$(_context4) {
188
+ while (1) {
189
+ switch (_context4.prev = _context4.next) {
190
+ case 0:
191
+ this.optimisticallyPerformStatusUpdate(itemOrItems, "unread", {
192
+ read_at: null
193
+ }, "unread_count");
194
+ return _context4.abrupt("return", this.makeStatusUpdate(itemOrItems, "unread"));
195
+
196
+ case 2:
197
+ case "end":
198
+ return _context4.stop();
199
+ }
200
+ }
201
+ }, _callee4, this);
202
+ }));
203
+
204
+ function markAsUnread(_x4) {
205
+ return _markAsUnread.apply(this, arguments);
206
+ }
207
+
208
+ return markAsUnread;
209
+ }()
210
+ }, {
211
+ key: "markAsArchived",
212
+ value: function () {
213
+ var _markAsArchived = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5(itemOrItems) {
214
+ var now;
215
+ return _regenerator["default"].wrap(function _callee5$(_context5) {
216
+ while (1) {
217
+ switch (_context5.prev = _context5.next) {
218
+ case 0:
219
+ now = new Date().toISOString();
220
+ this.optimisticallyPerformStatusUpdate(itemOrItems, "archived", {
221
+ archived_at: now
222
+ });
223
+ return _context5.abrupt("return", this.makeStatusUpdate(itemOrItems, "archived"));
224
+
225
+ case 3:
226
+ case "end":
227
+ return _context5.stop();
228
+ }
229
+ }
230
+ }, _callee5, this);
231
+ }));
232
+
233
+ function markAsArchived(_x5) {
234
+ return _markAsArchived.apply(this, arguments);
235
+ }
236
+
237
+ return markAsArchived;
238
+ }()
239
+ }, {
240
+ key: "markAsUnarchived",
241
+ value: function () {
242
+ var _markAsUnarchived = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6(itemOrItems) {
243
+ return _regenerator["default"].wrap(function _callee6$(_context6) {
244
+ while (1) {
245
+ switch (_context6.prev = _context6.next) {
246
+ case 0:
247
+ this.optimisticallyPerformStatusUpdate(itemOrItems, "unarchived", {
248
+ archived_at: null
249
+ });
250
+ return _context6.abrupt("return", this.makeStatusUpdate(itemOrItems, "unarchived"));
251
+
252
+ case 2:
253
+ case "end":
254
+ return _context6.stop();
255
+ }
256
+ }
257
+ }, _callee6, this);
258
+ }));
259
+
260
+ function markAsUnarchived(_x6) {
261
+ return _markAsUnarchived.apply(this, arguments);
262
+ }
263
+
264
+ return markAsUnarchived;
265
+ }()
266
+ /* Fetches the feed content, appending it to the store */
267
+
268
+ }, {
269
+ key: "fetch",
270
+ value: function () {
271
+ var _fetch = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee7() {
272
+ var options,
273
+ _this$store,
274
+ setState,
275
+ getState,
276
+ _getState,
277
+ networkStatus,
278
+ queryParams,
279
+ result,
280
+ response,
281
+ opts,
282
+ _opts,
283
+ _args7 = arguments;
284
+
285
+ return _regenerator["default"].wrap(function _callee7$(_context7) {
286
+ while (1) {
287
+ switch (_context7.prev = _context7.next) {
288
+ case 0:
289
+ options = _args7.length > 0 && _args7[0] !== undefined ? _args7[0] : {};
290
+ _this$store = this.store, setState = _this$store.setState, getState = _this$store.getState;
291
+ _getState = getState(), networkStatus = _getState.networkStatus; // If there's an existing request in flight, then do nothing
292
+
293
+ if (!(0, _networkStatus.isRequestInFlight)(networkStatus)) {
294
+ _context7.next = 5;
295
+ break;
296
+ }
297
+
298
+ return _context7.abrupt("return");
299
+
300
+ case 5:
301
+ // Set the loading type based on the request type it is
302
+ setState(function (store) {
303
+ var _options$__loadingTyp;
304
+
305
+ return store.setNetworkStatus((_options$__loadingTyp = options.__loadingType) !== null && _options$__loadingTyp !== void 0 ? _options$__loadingTyp : _networkStatus.NetworkStatus.loading);
306
+ }); // Always include the default params, if they have been set
307
+
308
+ queryParams = _objectSpread(_objectSpread({}, this.defaultOptions), options);
309
+ _context7.next = 9;
310
+ return this.apiClient.makeRequest({
311
+ method: "GET",
312
+ url: "/v1/users/".concat(this.knock.userId, "/feeds/").concat(this.feedId),
313
+ params: queryParams
314
+ });
315
+
316
+ case 9:
317
+ result = _context7.sent;
318
+
319
+ if (!(result.statusCode === "error")) {
320
+ _context7.next = 13;
321
+ break;
322
+ }
323
+
324
+ setState(function (store) {
325
+ return store.setNetworkStatus(_networkStatus.NetworkStatus.error);
326
+ });
327
+ return _context7.abrupt("return", {
328
+ status: result.statusCode,
329
+ data: result.error || result.body
330
+ });
331
+
332
+ case 13:
333
+ response = {
334
+ entries: result.body.entries,
335
+ meta: result.body.meta,
336
+ page_info: result.body.page_info
337
+ };
338
+
339
+ if (options.before) {
340
+ opts = {
341
+ shouldSetPage: false,
342
+ shouldAppend: true
343
+ };
344
+ setState(function (state) {
345
+ return state.setResult(response, opts);
346
+ });
347
+ } else if (options.after) {
348
+ _opts = {
349
+ shouldSetPage: true,
350
+ shouldAppend: true
351
+ };
352
+ setState(function (state) {
353
+ return state.setResult(response, _opts);
354
+ });
355
+ } else {
356
+ setState(function (state) {
357
+ return state.setResult(response);
358
+ });
359
+ }
360
+
361
+ this.broadcast("messages.new", response);
362
+ return _context7.abrupt("return", {
363
+ data: response,
364
+ status: result.statusCode
365
+ });
366
+
367
+ case 17:
368
+ case "end":
369
+ return _context7.stop();
370
+ }
371
+ }
372
+ }, _callee7, this);
373
+ }));
374
+
375
+ function fetch() {
376
+ return _fetch.apply(this, arguments);
377
+ }
378
+
379
+ return fetch;
380
+ }()
381
+ }, {
382
+ key: "fetchNextPage",
383
+ value: function () {
384
+ var _fetchNextPage = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee8() {
385
+ var getState, _getState2, pageInfo;
386
+
387
+ return _regenerator["default"].wrap(function _callee8$(_context8) {
388
+ while (1) {
389
+ switch (_context8.prev = _context8.next) {
390
+ case 0:
391
+ // Attempts to fetch the next page of results (if we have any)
392
+ getState = this.store.getState;
393
+ _getState2 = getState(), pageInfo = _getState2.pageInfo;
394
+
395
+ if (pageInfo.after) {
396
+ _context8.next = 4;
397
+ break;
398
+ }
399
+
400
+ return _context8.abrupt("return");
401
+
402
+ case 4:
403
+ this.fetch({
404
+ after: pageInfo.after,
405
+ __loadingType: _networkStatus.NetworkStatus.fetchMore
406
+ });
407
+
408
+ case 5:
409
+ case "end":
410
+ return _context8.stop();
411
+ }
412
+ }
413
+ }, _callee8, this);
414
+ }));
415
+
416
+ function fetchNextPage() {
417
+ return _fetchNextPage.apply(this, arguments);
418
+ }
419
+
420
+ return fetchNextPage;
421
+ }()
422
+ }, {
423
+ key: "broadcast",
424
+ value: function broadcast(eventName, data) {
425
+ this.broadcaster.emit(eventName, data);
426
+ } // Invoked when a new real-time message comes in from the socket
427
+
428
+ }, {
429
+ key: "onNewMessageReceived",
430
+ value: function () {
431
+ var _onNewMessageReceived = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee9(_ref) {
432
+ var metadata, _this$store2, getState, setState, currentHead;
433
+
434
+ return _regenerator["default"].wrap(function _callee9$(_context9) {
435
+ while (1) {
436
+ switch (_context9.prev = _context9.next) {
437
+ case 0:
438
+ metadata = _ref.metadata;
439
+ // Handle the new message coming in
440
+ _this$store2 = this.store, getState = _this$store2.getState, setState = _this$store2.setState;
441
+ currentHead = getState().items[0]; // Optimistically set the badge counts
442
+
443
+ setState(function (state) {
444
+ return state.setMetadata(metadata);
445
+ }); // Fetch the items before the current head (if it exists)
446
+
447
+ this.fetch({
448
+ before: currentHead === null || currentHead === void 0 ? void 0 : currentHead.__cursor
449
+ });
450
+
451
+ case 5:
452
+ case "end":
453
+ return _context9.stop();
454
+ }
455
+ }
456
+ }, _callee9, this);
457
+ }));
458
+
459
+ function onNewMessageReceived(_x7) {
460
+ return _onNewMessageReceived.apply(this, arguments);
461
+ }
462
+
463
+ return onNewMessageReceived;
464
+ }()
465
+ }, {
466
+ key: "buildUserFeedId",
467
+ value: function buildUserFeedId() {
468
+ return "".concat(this.feedId, ":").concat(this.knock.userId);
469
+ }
470
+ }, {
471
+ key: "optimisticallyPerformStatusUpdate",
472
+ value: function optimisticallyPerformStatusUpdate(itemOrItems, type, attrs, badgeCountAttr) {
473
+ var _this$store3 = this.store,
474
+ getState = _this$store3.getState,
475
+ setState = _this$store3.setState;
476
+ var itemIds = Array.isArray(itemOrItems) ? itemOrItems.map(function (item) {
477
+ return item.id;
478
+ }) : [itemOrItems.id];
479
+
480
+ if (badgeCountAttr) {
481
+ var _getState3 = getState(),
482
+ metadata = _getState3.metadata; // Tnis is a hack to determine the direction of whether we're
483
+ // adding or removing from the badge count
484
+
485
+
486
+ var direction = type.startsWith("un") ? itemIds.length : -itemIds.length;
487
+ setState(function (store) {
488
+ return store.setMetadata(_objectSpread(_objectSpread({}, metadata), {}, (0, _defineProperty2["default"])({}, badgeCountAttr, Math.max(0, metadata[badgeCountAttr] + direction))));
489
+ });
490
+ } // Update the items with the given attributes
491
+
492
+
493
+ setState(function (store) {
494
+ return store.setItemAttrs(itemIds, attrs);
495
+ });
496
+ }
497
+ }, {
498
+ key: "makeStatusUpdate",
499
+ value: function () {
500
+ var _makeStatusUpdate = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee10(itemOrItems, type) {
501
+ var itemIds, result;
502
+ return _regenerator["default"].wrap(function _callee10$(_context10) {
503
+ while (1) {
504
+ switch (_context10.prev = _context10.next) {
505
+ case 0:
506
+ if (!Array.isArray(itemOrItems)) {
507
+ _context10.next = 5;
508
+ break;
509
+ }
510
+
511
+ itemIds = itemOrItems.map(function (item) {
512
+ return item.id;
513
+ });
514
+ _context10.next = 4;
515
+ return this.apiClient.makeRequest({
516
+ method: "POST",
517
+ url: "/v1/messages/batch/".concat(type),
518
+ data: {
519
+ message_ids: itemIds
520
+ }
521
+ });
522
+
523
+ case 4:
524
+ return _context10.abrupt("return", _context10.sent);
525
+
526
+ case 5:
527
+ _context10.next = 7;
528
+ return this.apiClient.makeRequest({
529
+ method: type.startsWith("un") ? "DELETE" : "PUT",
530
+ url: "/v1/messages/".concat(itemOrItems.id, "/").concat(type)
531
+ });
532
+
533
+ case 7:
534
+ result = _context10.sent;
535
+ return _context10.abrupt("return", result);
536
+
537
+ case 9:
538
+ case "end":
539
+ return _context10.stop();
540
+ }
541
+ }
542
+ }, _callee10, this);
543
+ }));
544
+
545
+ function makeStatusUpdate(_x8, _x9) {
546
+ return _makeStatusUpdate.apply(this, arguments);
547
+ }
548
+
549
+ return makeStatusUpdate;
550
+ }()
551
+ }]);
552
+ return Feed;
553
+ }();
554
+
555
+ var _default = Feed;
556
+ exports["default"] = _default;
557
+ //# sourceMappingURL=feed.js.map