@knocklabs/client 0.8.14 → 0.8.16
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.
- package/README.md +22 -13
- package/dist/cjs/api.js +33 -56
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/clients/feed/feed.js +492 -624
- package/dist/cjs/clients/feed/feed.js.map +1 -1
- package/dist/cjs/clients/feed/index.js +1 -10
- package/dist/cjs/clients/feed/index.js.map +1 -1
- package/dist/cjs/clients/feed/interfaces.js.map +1 -1
- package/dist/cjs/clients/feed/store.js +4 -15
- package/dist/cjs/clients/feed/store.js.map +1 -1
- package/dist/cjs/clients/feed/types.js.map +1 -1
- package/dist/cjs/clients/feed/utils.js +0 -5
- package/dist/cjs/clients/feed/utils.js.map +1 -1
- package/dist/cjs/clients/preferences/index.js +216 -249
- package/dist/cjs/clients/preferences/index.js.map +1 -1
- package/dist/cjs/clients/preferences/interfaces.js.map +1 -1
- package/dist/cjs/clients/users/index.js +185 -0
- package/dist/cjs/clients/users/index.js.map +1 -0
- package/dist/cjs/clients/users/interfaces.js +6 -0
- package/dist/cjs/clients/users/interfaces.js.map +1 -0
- package/dist/cjs/index.js +15 -21
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interfaces.js.map +1 -1
- package/dist/cjs/knock.js +11 -21
- package/dist/cjs/knock.js.map +1 -1
- package/dist/cjs/networkStatus.js +3 -6
- package/dist/cjs/networkStatus.js.map +1 -1
- package/dist/esm/api.js +9 -21
- package/dist/esm/api.js.map +1 -1
- package/dist/esm/clients/feed/feed.js +69 -149
- package/dist/esm/clients/feed/feed.js.map +1 -1
- package/dist/esm/clients/feed/index.js +0 -5
- package/dist/esm/clients/feed/index.js.map +1 -1
- package/dist/esm/clients/feed/interfaces.js.map +1 -1
- package/dist/esm/clients/feed/store.js +2 -8
- package/dist/esm/clients/feed/store.js.map +1 -1
- package/dist/esm/clients/feed/types.js.map +1 -1
- package/dist/esm/clients/feed/utils.js +0 -1
- package/dist/esm/clients/feed/utils.js.map +1 -1
- package/dist/esm/clients/preferences/index.js +35 -25
- package/dist/esm/clients/preferences/index.js.map +1 -1
- package/dist/esm/clients/preferences/interfaces.js.map +1 -1
- package/dist/esm/clients/users/index.js +84 -0
- package/dist/esm/clients/users/index.js.map +1 -0
- package/dist/esm/clients/users/interfaces.js +2 -0
- package/dist/esm/clients/users/interfaces.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interfaces.js.map +1 -1
- package/dist/esm/knock.js +10 -22
- package/dist/esm/knock.js.map +1 -1
- package/dist/esm/networkStatus.js +3 -5
- package/dist/esm/networkStatus.js.map +1 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/clients/feed/feed.d.ts +1 -1
- package/dist/types/clients/feed/feed.d.ts.map +1 -1
- package/dist/types/clients/feed/interfaces.d.ts +2 -1
- package/dist/types/clients/feed/interfaces.d.ts.map +1 -1
- package/dist/types/clients/feed/types.d.ts +10 -10
- package/dist/types/clients/feed/types.d.ts.map +1 -1
- package/dist/types/clients/preferences/index.d.ts +27 -0
- package/dist/types/clients/preferences/index.d.ts.map +1 -1
- package/dist/types/clients/preferences/interfaces.d.ts +7 -4
- package/dist/types/clients/preferences/interfaces.d.ts.map +1 -1
- package/dist/types/clients/users/index.d.ts +16 -0
- package/dist/types/clients/users/index.d.ts.map +1 -0
- package/dist/types/clients/users/interfaces.d.ts +8 -0
- package/dist/types/clients/users/interfaces.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/interfaces.d.ts +6 -2
- package/dist/types/interfaces.d.ts.map +1 -1
- package/dist/types/knock.d.ts +2 -0
- package/dist/types/knock.d.ts.map +1 -1
- package/package.json +15 -10
package/README.md
CHANGED
|
@@ -43,10 +43,6 @@ knockClient.authenticate(
|
|
|
43
43
|
);
|
|
44
44
|
```
|
|
45
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
46
|
### Retrieving new items from the feed
|
|
51
47
|
|
|
52
48
|
```typescript
|
|
@@ -159,7 +155,7 @@ feedClient.markAsUnarchived(feedItemOrItems);
|
|
|
159
155
|
|
|
160
156
|
```typescript
|
|
161
157
|
// Set an entire preference set
|
|
162
|
-
await knockClient.
|
|
158
|
+
await knockClient.user.setPreferences({
|
|
163
159
|
channel_types: { email: true, sms: false },
|
|
164
160
|
workflows: {
|
|
165
161
|
"dinosaurs-loose": {
|
|
@@ -169,16 +165,29 @@ await knockClient.preferences.set({
|
|
|
169
165
|
});
|
|
170
166
|
|
|
171
167
|
// Retrieve a whole preference set
|
|
172
|
-
const preferences = await knockClient.
|
|
168
|
+
const preferences = await knockClient.user.getPreferences();
|
|
169
|
+
|
|
170
|
+
// Retrieve all preference sets for the user
|
|
171
|
+
const preferenceSets = await knockClient.user.getAllPreferences();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Managing the current user's channel data
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
```typescript
|
|
177
|
+
// Get user channel data
|
|
178
|
+
const channelData = await knockClient.user.getChannelData({
|
|
179
|
+
channelId: "uuid-knock-channel-id",
|
|
180
|
+
});
|
|
181
|
+
```
|
|
176
182
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
```typescript
|
|
184
|
+
// Set push channel data for a user
|
|
185
|
+
await knockClient.user.setChannelData({
|
|
186
|
+
channelId: "uuid-knock-channel-id",
|
|
187
|
+
channelData: {
|
|
188
|
+
tokens: ["apns-user-push-token"],
|
|
182
189
|
},
|
|
183
190
|
});
|
|
184
191
|
```
|
|
192
|
+
|
|
193
|
+
See provider requirements for setting channel data [here]("https://docs.knock.app/managing-recipients/setting-channel-data#provider-data-requirements").
|
package/dist/cjs/api.js
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
-
|
|
5
4
|
Object.defineProperty(exports, "__esModule", {
|
|
6
5
|
value: true
|
|
7
6
|
});
|
|
8
7
|
exports["default"] = void 0;
|
|
9
|
-
|
|
10
8
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
-
|
|
12
9
|
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
-
|
|
14
10
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
15
|
-
|
|
16
11
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
17
|
-
|
|
18
12
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
19
|
-
|
|
20
13
|
var _axios = _interopRequireDefault(require("axios"));
|
|
21
|
-
|
|
22
14
|
var _axiosRetry = _interopRequireDefault(require("axios-retry"));
|
|
23
|
-
|
|
24
15
|
var _phoenix = require("phoenix");
|
|
25
|
-
|
|
26
16
|
var ApiClient = /*#__PURE__*/function () {
|
|
27
17
|
function ApiClient(options) {
|
|
28
18
|
(0, _classCallCheck2["default"])(this, ApiClient);
|
|
@@ -33,8 +23,9 @@ var ApiClient = /*#__PURE__*/function () {
|
|
|
33
23
|
(0, _defineProperty2["default"])(this, "socket", void 0);
|
|
34
24
|
this.host = options.host;
|
|
35
25
|
this.apiKey = options.apiKey;
|
|
36
|
-
this.userToken = options.userToken || null;
|
|
26
|
+
this.userToken = options.userToken || null;
|
|
37
27
|
|
|
28
|
+
// Create a retryable axios client
|
|
38
29
|
this.axiosClient = _axios["default"].create({
|
|
39
30
|
baseURL: this.host,
|
|
40
31
|
headers: {
|
|
@@ -44,7 +35,6 @@ var ApiClient = /*#__PURE__*/function () {
|
|
|
44
35
|
"X-Knock-User-Token": this.userToken
|
|
45
36
|
}
|
|
46
37
|
});
|
|
47
|
-
|
|
48
38
|
if (typeof window !== "undefined") {
|
|
49
39
|
this.socket = new _phoenix.Socket("".concat(this.host.replace("http", "ws"), "/ws/v1"), {
|
|
50
40
|
params: {
|
|
@@ -53,59 +43,50 @@ var ApiClient = /*#__PURE__*/function () {
|
|
|
53
43
|
}
|
|
54
44
|
});
|
|
55
45
|
}
|
|
56
|
-
|
|
57
46
|
(0, _axiosRetry["default"])(this.axiosClient, {
|
|
58
47
|
retries: 3,
|
|
59
48
|
retryCondition: this.canRetryRequest,
|
|
60
49
|
retryDelay: _axiosRetry["default"].exponentialDelay
|
|
61
50
|
});
|
|
62
51
|
}
|
|
63
|
-
|
|
64
52
|
(0, _createClass2["default"])(ApiClient, [{
|
|
65
53
|
key: "makeRequest",
|
|
66
54
|
value: function () {
|
|
67
55
|
var _makeRequest = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(req) {
|
|
68
56
|
var result;
|
|
69
57
|
return _regenerator["default"].wrap(function _callee$(_context) {
|
|
70
|
-
while (1) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
result
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
case 11:
|
|
98
|
-
case "end":
|
|
99
|
-
return _context.stop();
|
|
100
|
-
}
|
|
58
|
+
while (1) switch (_context.prev = _context.next) {
|
|
59
|
+
case 0:
|
|
60
|
+
_context.prev = 0;
|
|
61
|
+
_context.next = 3;
|
|
62
|
+
return this.axiosClient(req);
|
|
63
|
+
case 3:
|
|
64
|
+
result = _context.sent;
|
|
65
|
+
return _context.abrupt("return", {
|
|
66
|
+
statusCode: result.status < 300 ? "ok" : "error",
|
|
67
|
+
body: result.data,
|
|
68
|
+
error: undefined,
|
|
69
|
+
status: result.status
|
|
70
|
+
});
|
|
71
|
+
case 7:
|
|
72
|
+
_context.prev = 7;
|
|
73
|
+
_context.t0 = _context["catch"](0);
|
|
74
|
+
console.error(_context.t0);
|
|
75
|
+
return _context.abrupt("return", {
|
|
76
|
+
statusCode: "error",
|
|
77
|
+
status: 500,
|
|
78
|
+
body: undefined,
|
|
79
|
+
error: _context.t0
|
|
80
|
+
});
|
|
81
|
+
case 11:
|
|
82
|
+
case "end":
|
|
83
|
+
return _context.stop();
|
|
101
84
|
}
|
|
102
85
|
}, _callee, this, [[0, 7]]);
|
|
103
86
|
}));
|
|
104
|
-
|
|
105
87
|
function makeRequest(_x) {
|
|
106
88
|
return _makeRequest.apply(this, arguments);
|
|
107
89
|
}
|
|
108
|
-
|
|
109
90
|
return makeRequest;
|
|
110
91
|
}()
|
|
111
92
|
}, {
|
|
@@ -115,28 +96,24 @@ var ApiClient = /*#__PURE__*/function () {
|
|
|
115
96
|
if (_axiosRetry["default"].isNetworkError(error)) {
|
|
116
97
|
return true;
|
|
117
98
|
}
|
|
118
|
-
|
|
119
99
|
if (!error.response) {
|
|
120
100
|
// Cannot determine if the request can be retried
|
|
121
101
|
return false;
|
|
122
|
-
}
|
|
123
|
-
|
|
102
|
+
}
|
|
124
103
|
|
|
104
|
+
// Retry Server Errors (5xx).
|
|
125
105
|
if (error.response.status >= 500 && error.response.status <= 599) {
|
|
126
106
|
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
107
|
+
}
|
|
129
108
|
|
|
109
|
+
// Retry if rate limited.
|
|
130
110
|
if (error.response.status === 429) {
|
|
131
111
|
return true;
|
|
132
112
|
}
|
|
133
|
-
|
|
134
113
|
return false;
|
|
135
114
|
}
|
|
136
115
|
}]);
|
|
137
116
|
return ApiClient;
|
|
138
117
|
}();
|
|
139
|
-
|
|
140
|
-
var _default = ApiClient;
|
|
141
|
-
exports["default"] = _default;
|
|
118
|
+
var _default = exports["default"] = ApiClient;
|
|
142
119
|
//# sourceMappingURL=api.js.map
|
package/dist/cjs/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"api.js","names":["_axios","_interopRequireDefault","require","_axiosRetry","_phoenix","ApiClient","options","_classCallCheck2","_defineProperty2","host","apiKey","userToken","axiosClient","axios","create","baseURL","headers","Accept","Authorization","concat","window","socket","Socket","replace","params","user_token","api_key","axiosRetry","retries","retryCondition","canRetryRequest","retryDelay","exponentialDelay","_createClass2","key","value","_makeRequest","_asyncToGenerator2","_regenerator","mark","_callee","req","result","wrap","_callee$","_context","prev","next","sent","abrupt","statusCode","status","body","data","error","undefined","t0","console","stop","makeRequest","_x","apply","arguments","isNetworkError","response","_default","exports"],"sources":["../../src/api.ts"],"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 // eslint-disable-next-line\n error?: any;\n // eslint-disable-next-line\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\n public socket: Socket | undefined;\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 if (typeof window !== \"undefined\") {\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\n axiosRetry(this.axiosClient, {\n retries: 3,\n retryCondition: this.canRetryRequest,\n retryDelay: axiosRetry.exponentialDelay,\n });\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\n // eslint:disable-next-line\n } catch (e: unknown) {\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"],"mappings":"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"}
|