product_os_command_control/registry.rs
1use core::str::FromStr;
2use std::prelude::v1::*;
3
4use std::collections::BTreeMap;
5use std::sync::Arc;
6use serde::{ Deserialize, Serialize };
7
8use product_os_capabilities::{ Features, Services };
9use product_os_security::{AsByteVector, DHKeyStore, RandomGenerator, certificates::Certificates, RandomGeneratorTemplate, RNG, StdRng, SeedableRng};
10use product_os_store::ProductOSKeyValueStore;
11
12use chrono::{DateTime, Utc };
13use parking_lot::Mutex;
14use product_os_request::Uri;
15
16#[derive(Debug, Deserialize, Serialize)]
17#[serde(rename_all = "camelCase")]
18enum Protocol {
19 HTTP,
20 HTTPS,
21 UNIX
22}
23
24
25#[derive(Debug, Deserialize, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct Node {
28 id: uuid::Uuid,
29 machine_id: String,
30
31 uri: String,
32 process_id: u32,
33
34 certificate: Vec<u8>,
35
36 capabilities: Vec<String>,
37 services: Services,
38 features: Features,
39
40 failures: u8,
41 created_at: DateTime<Utc>,
42 updated_at: DateTime<Utc>
43}
44
45impl AsByteVector for &Node {
46 fn as_byte_vector(&self) -> Vec<u8> {
47 let mut bytes = vec!();
48
49 bytes.extend_from_slice(self.id.clone().as_bytes());
50 bytes.extend_from_slice(self.machine_id.clone().as_bytes());
51 bytes.extend_from_slice(self.uri.to_string().as_bytes());
52 bytes.extend_from_slice(self.certificate.clone().as_slice());
53 bytes.extend_from_slice(&[self.failures.clone().clone()]);
54 bytes.extend_from_slice(self.created_at.to_string().as_bytes());
55 bytes.extend_from_slice(self.updated_at.to_string().as_bytes());
56 // bytes.extend_from_slice();
57
58 bytes
59 }
60}
61
62
63impl Node {
64 pub fn default(config: &product_os_configuration::Configuration, certificates: Certificates) -> Self {
65 let machine_uid = match machine_uid::get() {
66 Ok(uid) => uid,
67 Err(e) => panic!("Unable to generate machine id: {}", e)
68 };
69
70 Self {
71 id: uuid::Uuid::new_v4(),
72 uri: Uri::from_str(config.url_address().as_str()).unwrap().to_string(),
73 process_id: std::process::id(),
74 machine_id: product_os_security::create_string_hash(machine_uid.as_str()),
75 certificate: certificates.certificates.first().unwrap().to_owned(),
76 capabilities: Vec::new(),
77 services: Services::new(),
78 features: Features::new(),
79 failures: 0,
80 created_at: Utc::now(),
81 updated_at: Utc::now()
82 }
83 }
84
85 pub fn get_identifier(&self) -> String {
86 self.id.to_string()
87 }
88
89 pub fn get_protocol(&self) -> String {
90 let uri = Uri::from_str(self.uri.as_str()).unwrap();
91 match uri.scheme() {
92 None => String::new(),
93 Some(s) => s.to_string()
94 }
95 }
96
97 pub fn get_address(&self) -> Uri {
98 Uri::from_str(self.uri.as_str()).unwrap()
99 }
100
101 pub fn get_process_id(&self) -> u32 {
102 self.process_id
103 }
104
105 pub fn get_certificate(&self) -> Vec<u8> {
106 self.certificate.to_owned()
107 }
108
109 pub fn get_failures(&self) -> u8 {
110 self.failures
111 }
112
113 pub fn get_features(&self) -> &Features {
114 &self.features
115 }
116
117 pub fn get_services(&self) -> &Services {
118 &self.services
119 }
120
121 pub fn match_node(&self, selector: &str, search_value: &str) -> bool {
122 let mut matched = true;
123
124 let search = selector;
125 let value = search_value;
126
127 match search {
128 "feature" => {
129 match self.features.get(value) {
130 Some(_) => (),
131 None => matched = false
132 }
133 },
134 "capability" => {
135 for capability in &self.capabilities {
136 if capability != value { matched = false; }
137 }
138 },
139 "service.kind" => {
140 match self.services.find(value.to_string()) {
141 Some(_) => (),
142 None => matched = false
143 }
144 },
145 "service.enabled" => {
146 for (_, service) in self.services.list() {
147 if service.enabled.to_string() != value { matched = false; }
148 }
149 },
150 "service.active" => {
151 for (_, service) in self.services.list() {
152 if service.active.to_string() != value { matched = false; }
153 }
154 },
155 _ => ()
156 }
157
158 matched
159 }
160
161 pub fn match_node_query(&self, query: &BTreeMap<&str, &str>) -> bool {
162 let mut matched = true;
163
164 for (s, v) in query {
165 let search = s;
166 let value = v;
167
168 if !self.match_node(search, value) { matched = false }
169 }
170
171 matched
172 }
173
174 pub fn get_created_at(&self) -> DateTime<Utc> {
175 self.created_at
176 }
177
178 pub fn get_last_updated_at(&self) -> DateTime<Utc> {
179 self.updated_at
180 }
181}
182
183
184
185
186pub struct Registry {
187 me: Node,
188 nodes: BTreeMap<String, Node>,
189 key_store: DHKeyStore,
190
191 store: Arc<ProductOSKeyValueStore>,
192
193 max_failures: u8
194}
195
196impl Registry {
197 pub fn new(config: &product_os_configuration::Configuration, key_value_store: Arc<ProductOSKeyValueStore>, certificates: Certificates) -> Self {
198 let me = Node::default(config, certificates);
199
200 let registry = Registry {
201 me,
202 nodes: BTreeMap::new(),
203 key_store: DHKeyStore::new(),
204 store: key_value_store,
205 max_failures: config.get_cc_max_failures(),
206 };
207
208 registry
209 }
210
211 pub fn get_max_failures(&self) -> u8 {
212 self.max_failures.to_owned()
213 }
214
215 async fn upsert_me_remote(&mut self) {
216 self.store.group_set(self.me.id.to_string().as_str(), serde_json::to_string(&self.me).unwrap().as_str()).unwrap_or_default()
217 }
218
219 async fn upsert_node_remote(&mut self, node: &Node) {
220 tracing::info!("Upserting node: {:?}", node);
221 self.store.group_set(node.id.to_string().as_str(), serde_json::to_string(node).unwrap().as_str()).unwrap_or_default();
222 }
223
224 async fn remove_node_remote(&mut self, identifier: &str) {
225 self.store.group_remove(identifier).unwrap_or_default()
226 }
227
228 async fn get_node_remote(&mut self, id: &str) -> Option<Node> {
229 match self.store.group_get(id) {
230 Ok(v) => {
231 let mut node: Node = serde_json::from_str(v.as_str()).unwrap();
232 node.features.setup_router();
233 Some(node)
234 },
235 Err(_) => None
236 }
237 }
238
239 pub async fn check_me_remote(&mut self) -> Option<&Node> {
240 let id = self.me.id.to_string();
241
242 match self.get_node_remote(id.as_str()).await {
243 Some(node) => {
244 if node.id != self.me.id {
245 // Kill or try to do a revival of node - lost for some reason
246 None
247 }
248 else {
249 Some(&self.me)
250 }
251 },
252 None => { None }
253 }
254 }
255
256 /****** All other features query the local registry first ********/
257
258 pub fn get_me(&self) -> &Node {
259 &self.me
260 }
261
262 pub async fn update_me(&mut self) {
263 self.upsert_me_remote().await;
264 }
265
266 pub fn update_me_status(&mut self, success: bool) -> bool {
267 let failures = if success { 0 } else { self.me.failures.to_owned() + 1 };
268
269 if failures < self.max_failures {
270 self.me.failures = failures;
271 self.me.updated_at = Utc::now();
272
273 true
274 }
275 else {
276 false
277 }
278 }
279
280 pub async fn update_pulse_status(&mut self, id: &str, success: bool) -> bool {
281 match self.get_node_remote(id).await {
282 Some(mut node) => {
283 let failures = if success { 0 } else { node.failures + 1 };
284
285 if failures < self.max_failures {
286 node.failures = failures;
287 node.updated_at = Utc::now();
288
289 self.upsert_node_remote(&node).await;
290 self.nodes.insert(node.id.to_string(), node);
291
292 true
293 }
294 else {
295 tracing::info!("Removing node due to failures count {}: {:?}", failures, node);
296 self.remove_node(node.id.to_string().as_str()).await;
297
298 false
299 }
300 },
301 None => false
302 }
303 }
304
305 pub fn upsert_node_local(&mut self, identifier: String, mut node: Node) {
306 node.features.setup_router();
307 self.nodes.insert(identifier, node);
308 }
309
310 pub fn find_nodes(&self, query: BTreeMap<&str, &str>, exclude_me: bool) -> BTreeMap<String, &Node> {
311 let mut result = BTreeMap::new();
312 let me = self.me.id.to_string();
313
314 for (id, node) in &self.nodes {
315 if (!exclude_me || (exclude_me && !me.eq(id))) && node.match_node_query(&query) { result.insert(id.to_string(), node); }
316 }
317
318 result
319 }
320
321 pub fn get_node(&self, id: &str) -> Option<&Node> {
322 self.nodes.get(id)
323 }
324
325 pub fn get_nodes(&self, skip: u8, exclude_me: bool) -> BTreeMap<String, &Node> {
326 let mut nodes = BTreeMap::new();
327 let mut count = 0;
328 let me = self.me.id.to_string();
329
330 for (id, node) in self.nodes.iter() {
331 if (!exclude_me || (exclude_me && !me.eq(id))) && count >= skip { nodes.insert(id.to_string(), node); }
332 count = count + 1;
333 }
334
335 nodes
336 }
337
338 pub fn get_nodes_certificates(&self, skip: u8, exclude_me: bool) -> Vec<Vec<u8>> {
339 let mut certificates = Vec::new();
340 let mut count = 0;
341 let me = self.me.id.to_string();
342
343 for (id, node) in self.nodes.iter() {
344 if (!exclude_me || (exclude_me && !me.eq(id))) && count >= skip { certificates.push(node.get_certificate()) }
345 count = count + 1;
346 }
347
348 certificates
349 }
350
351 pub fn get_nodes_raw_certificates(&self, skip: u8, exclude_me: bool) -> Vec<Vec<u8>> {
352 let mut certificates = Vec::new();
353 let mut count = 0;
354 let me = self.me.id.to_string();
355
356 for (id, node) in self.nodes.iter() {
357 if (!exclude_me || (exclude_me && !me.eq(id))) && count >= skip { certificates.push(node.get_certificate()) }
358 count = count + 1;
359 }
360
361 certificates
362 }
363
364 pub fn get_nodes_endpoints(&self, skip: u8, exclude_me: bool) -> BTreeMap<String, (Uri, Option<Vec<u8>>)> {
365 let mut node_map = BTreeMap::new();
366 let mut count = 0;
367 let me = self.me.id.to_string();
368
369 for (id, node) in self.nodes.iter() {
370 if (!exclude_me || (exclude_me && !me.eq(id))) && count >= skip {
371 let address = node.get_address();
372 let key = self.get_key(id);
373
374 node_map.insert(id.to_string(), (address, key));
375 }
376
377 count = count + 1;
378 }
379
380 node_map
381 }
382
383 pub async fn remove_node(&mut self, identifier: &str) -> Option<Node> {
384 match self.nodes.remove(identifier) {
385 Some(node) => {
386 self.remove_node_remote(node.id.to_string().as_str()).await;
387 Some(node)
388 },
389 None => None
390 }
391 }
392
393 pub fn pick_node(&self, query: BTreeMap<&str, &str>) -> Option<&Node> {
394 let eligible_nodes = Vec::from_iter(self.find_nodes(query, false).into_iter());
395 let select = RandomGenerator::new(Some(RNG::Std(StdRng::from_entropy()))).get_random_usize(0, eligible_nodes.len());
396
397 match eligible_nodes.get(select) {
398 Some(value) => Some(value.1),
399 None => None
400 }
401 }
402
403 pub fn pick_node_for_capability(&self, capability: &str) -> Option<&Node> {
404 let mut query = BTreeMap::new();
405 query.insert("capability", capability);
406 self.pick_node(query)
407 }
408
409 pub async fn add_feature(&mut self, feature: Arc<dyn product_os_capabilities::Feature>, base_path: String, router: &mut product_os_router::ProductOSRouter) {
410 self.me.features.add(feature, base_path, router).await;
411 self.update_me().await;
412 }
413
414 pub async fn add_feature_mut(&mut self, feature: Arc<Mutex<dyn product_os_capabilities::Feature>>, base_path: String, router: &mut product_os_router::ProductOSRouter) {
415 self.me.features.add_mut(feature, base_path, router).await;
416 self.update_me().await;
417 }
418
419 pub fn pick_node_for_feature(&self, feature: &str) -> Option<&Node> {
420 let mut query = BTreeMap::new();
421 query.insert("feature", feature);
422 self.pick_node(query)
423 }
424
425 pub async fn remove_feature(&mut self, identifier: &str) {
426 self.me.features.remove(identifier);
427 self.update_me().await;
428 }
429
430 pub async fn add_service(&mut self, service: Arc<dyn product_os_capabilities::Service>) {
431 self.me.services.add(service).await;
432 self.update_me().await;
433 }
434
435 pub async fn add_service_mut(&mut self, service: Arc<Mutex<dyn product_os_capabilities::Service>>) {
436 self.me.services.add_mut(service).await;
437 self.update_me().await;
438 }
439
440 pub async fn set_service_active(&mut self, identifier: String, status: bool) {
441 let id = identifier.as_str();
442 match self.me.services.get_mut(id) {
443 None => (),
444 Some(s) => {
445 s.active = status;
446 self.update_me().await;
447 }
448 }
449 }
450
451 pub async fn remove_service(&mut self, identifier: &str) {
452 self.me.services.remove(identifier);
453 self.update_me().await;
454 }
455
456 pub async fn remove_inactive_services(&mut self, query: BTreeMap<&str, &str>) {
457 let mut matches = Vec::new();
458
459 for (identifier, _) in self.find_nodes(query, true) {
460 matches.push(identifier.to_owned());
461 }
462
463 for identifier in &matches {
464 self.remove_service(identifier).await;
465 }
466
467 if matches.len() > 0 { self.update_me().await };
468 }
469
470 pub async fn start_services(&mut self) {
471 for (_, service) in self.me.services.list_mut() {
472 match service.start().await {
473 Ok(_) => {}
474 Err(_) => {}
475 }
476 }
477 }
478
479 pub async fn discover_nodes(&mut self) {
480 let mut nodes = BTreeMap::new();
481
482 match self.store.group_find(None) {
483 Ok(ns) => {
484 nodes = ns;
485 },
486 Err(_) => {
487 tracing::error!("Error getting nodes from store store");
488 }
489 }
490
491 for (id, node) in nodes {
492 match serde_json::from_str(node.as_str()) {
493 Ok(n) => {
494 let mut node: Node = n;
495 node.features.setup_router();
496 tracing::trace!("Importing remote node: {:?}", node.id);
497 self.upsert_node_local(node.id.to_string(), node);
498 },
499 Err(e) => {
500 tracing::error!("Error importing remote node {} - purging: {:?}", id, e);
501 self.remove_node_remote(id.as_str()).await;
502 }
503 }
504 }
505 }
506
507 pub fn get_key(&self, identifier: &str) -> Option<Vec<u8>> {
508 match self.key_store.get_key(identifier) {
509 Some(k) => Some(k.to_vec()),
510 None => None
511 }
512 }
513
514 pub fn create_key_session(&mut self) -> (String, [u8; 32]) {
515 self.key_store.create_session()
516 }
517
518 pub fn generate_key(&mut self, session_identifier: &str, remote_public_key: &[u8], association: String, remote_session_identifier: Option<String>) {
519 self.key_store.generate_key(session_identifier, remote_public_key, association, remote_session_identifier);
520 }
521}
522
523
524/*
525{
526 "_id": {
527 "$oid": "6160ed1181d34b02de2054d1"
528 },
529 "capabilities": [
530 "updateUsersStatus",
531 "cleanUpManagers",
532 "queue"
533 ],
534 "machineID": "bd674848a60c3e19cf25764bb5d6b1a124a46c344f9a30573b60e4fbb6615e2d",
535 "key": "Z_m=2Wg(0HD4c-%%Jkqu>,H0<a&3TJ>s",
536 "services": {
537 "authentication": {
538 "identifier": "authentication",
539 "path": "/authentication",
540 "remotes": {
541 "https://172.18.0.9:8421": ",J>mM]z>wnQQ4-nT?a/J4`XpU]Pjy1/d",
542 "https://172.18.0.3:8989": "Yf/bE`*O>3;^HsWj(&_<!v(Kq;HT9?[{",
543 "https://172.18.0.5:8888": "fcVk[[O65<FwRrsJwDnnxD!y`^m]B~S`",
544 "https://172.18.0.4:9102": "@P~3?^}6_}yph{@/L|a+tW7I#2^(O]cJ",
545 "https://172.18.0.2:9410": "}ub%oUYN)9e/<<qL%*|m1`f]c(*t!Rc7",
546 "https://172.18.0.8:8443": ".#z@4(_Q_o(yO#R!aDIR3(z-t[PeRgTN",
547 "https://172.18.0.7:9337": "(1|)v|D?24?pR4,tc*a{bI]ue-UHPw`_"
548 }
549 },
550 "servers": {
551 "identifier": "servers",
552 "path": "/servers",
553 "remotes": {
554 "https://172.18.0.9:8421": ",J>mM]z>wnQQ4-nT?a/J4`XpU]Pjy1/d",
555 "https://172.18.0.3:8989": "Yf/bE`*O>3;^HsWj(&_<!v(Kq;HT9?[{",
556 "https://172.18.0.5:8888": "fcVk[[O65<FwRrsJwDnnxD!y`^m]B~S`",
557 "https://172.18.0.4:9102": "@P~3?^}6_}yph{@/L|a+tW7I#2^(O]cJ",
558 "https://172.18.0.2:9410": "}ub%oUYN)9e/<<qL%*|m1`f]c(*t!Rc7",
559 "https://172.18.0.8:8443": ".#z@4(_Q_o(yO#R!aDIR3(z-t[PeRgTN",
560 "https://172.18.0.7:9337": "(1|)v|D?24?pR4,tc*a{bI]ue-UHPw`_"
561 }
562 }
563 },
564 "features": {
565 "service-cache": {
566 "identifier": "service-cache",
567 "paths": [
568 "/cache*",
569 "/cache/\*"
570 ],
571 "remotes": {
572 "https://172.18.0.9:8421": ",J>mM]z>wnQQ4-nT?a/J4`XpU]Pjy1/d",
573 "https://172.18.0.3:8989": "Yf/bE`*O>3;^HsWj(&_<!v(Kq;HT9?[{",
574 "https://172.18.0.5:8888": "fcVk[[O65<FwRrsJwDnnxD!y`^m]B~S`",
575 "https://172.18.0.4:9102": "@P~3?^}6_}yph{@/L|a+tW7I#2^(O]cJ",
576 "https://172.18.0.2:9410": "}ub%oUYN)9e/<<qL%*|m1`f]c(*t!Rc7",
577 "https://172.18.0.8:8443": ".#z@4(_Q_o(yO#R!aDIR3(z-t[PeRgTN",
578 "https://172.18.0.7:9337": "(1|)v|D?24?pR4,tc*a{bI]ue-UHPw`_"
579 }
580 },
581 "command/endpoint": {
582 "identifier": "command/endpoint",
583 "paths": [
584 "/command/:service/:instruction"
585 ],
586 "remotes": {
587 "https://172.18.0.9:8421": ",J>mM]z>wnQQ4-nT?a/J4`XpU]Pjy1/d",
588 "https://172.18.0.3:8989": "Yf/bE`*O>3;^HsWj(&_<!v(Kq;HT9?[{",
589 "https://172.18.0.5:8888": "fcVk[[O65<FwRrsJwDnnxD!y`^m]B~S`",
590 "https://172.18.0.4:9102": "@P~3?^}6_}yph{@/L|a+tW7I#2^(O]cJ",
591 "https://172.18.0.2:9410": "}ub%oUYN)9e/<<qL%*|m1`f]c(*t!Rc7",
592 "https://172.18.0.8:8443": ".#z@4(_Q_o(yO#R!aDIR3(z-t[PeRgTN",
593 "https://172.18.0.7:9337": "(1|)v|D?24?pR4,tc*a{bI]ue-UHPw`_"
594 }
595 }
596 },
597 "services": [
598 {
599 "_id": {
600 "$oid": "6160ed1481d34b02de205502"
601 },
602 "identifier": "queue0",
603 "key": "r@a{mjm{Q#ccwQhnQC7NB8/i,j^QUUCp",
604 "type": "queue",
605 "active": true,
606 "enabled": true,
607 "updatedAt": {
608 "$date": "2021-10-09T01:15:00.336Z"
609 },
610 "createdAt": {
611 "$date": "2021-10-09T01:15:00.274Z"
612 }
613 }
614 ],
615 "failures": 0,
616 "addressV4": "172.18.0.6",
617 "port": 9751,
618 "processID": "734",
619 "createdAt": {
620 "$date": "2021-10-09T01:14:57.245Z"
621 },
622 "updatedAt": {
623 "$date": "2021-10-09T08:58:00.373Z"
624 },
625 "__v": 0,
626 "remoteFeatures": {
627 "events/execute": {
628 "identifier": "events/execute",
629 "paths": [
630 "/api/events/trigger",
631 "/api/events/notification",
632 "/api/events/process"
633 ]
634 },
635 "flows/execute": {
636 "identifier": "flows/execute",
637 "paths": [
638 "/api/flows/execute"
639 ]
640 },
641 "conditions/execute": {
642 "identifier": "conditions/execute",
643 "paths": [
644 "/api/conditions/execute"
645 ]
646 },
647 "effects/execute": {
648 "identifier": "effects/execute",
649 "paths": [
650 "/api/effect/trigger",
651 "/api/effects/trigger",
652 "/api/effects/process"
653 ]
654 },
655 "authentication": {
656 "identifier": "authentication",
657 "paths": [
658 "/do-login/credentials",
659 "/do-login/token",
660 "/do-logout"
661 ]
662 },
663 "oidc2": {
664 "identifier": "oidc2",
665 "paths": [
666 "/oidc2/authorize",
667 "/oidc2/authorize/decision",
668 "/oidc2/verify",
669 "/oidc2/whoami",
670 "/oidc2/invalidate",
671 "/oidc2/token",
672 "/oidc2/refresh"
673 ]
674 },
675 "intelligent/type": {
676 "identifier": "intelligent/type",
677 "paths": [
678 "/api/intelligent/agent/categories/:name",
679 "/api/intelligent/agent/list/predefined",
680 "/api/intelligent/agent/list/userdefined"
681 ]
682 },
683 "ipreveal": {
684 "identifier": "ipreveal",
685 "paths": [
686 "/api/ipreveal/:experience/:ipaddress",
687 "/api/ipreveal/dataset/update/all"
688 ]
689 },
690 "analyzer": {
691 "identifier": "analyzer",
692 "paths": [
693 "/api/analytics/process"
694 ]
695 },
696 "processor": {
697 "identifier": "processor",
698 "paths": [
699 "/api/process/reaction",
700 "/api/process/signal"
701 ]
702 },
703 "tiny": {
704 "identifier": "tiny",
705 "paths": [
706 "/go/:tinyID"
707 ]
708 },
709 "talk": {
710 "identifier": "talk",
711 "paths": [
712 "/talk/load",
713 "/talk/style",
714 "/talk/:appID",
715 "/talk/:appID/resume"
716 ]
717 },
718 "responses/execute": {
719 "identifier": "responses/execute",
720 "paths": [
721 "/api/response/send/:key/:identifier/:uuid/:channel",
722 "/api/response/receive/:key/:identifier/:uuid"
723 ]
724 },
725 "media/get": {
726 "identifier": "media/get",
727 "paths": [
728 "/media/avatar/bot/:appID/:identifier",
729 "/media/image/:identifier",
730 "/media/file/:identifier",
731 "/media/video/:identifier",
732 "/media/audio/:identifier",
733 "/media/:identifier"
734 ]
735 },
736 "shared": {
737 "identifier": "shared",
738 "paths": [
739 "/shared/product-os/blog/rss",
740 "/shared/tracking/google",
741 "/shared/location/display/map",
742 "/shared/experience/loading",
743 "/shared/product-os/preview",
744 "/shared/product-os/chat",
745 "/shared/live/chat",
746 "/shared/courier/chat",
747 "/shared/product-os/settings",
748 "/shared/product-os/load",
749 "/shared/product-os/launch",
750 "/shared/product-os/journey/load",
751 "/shared/product-os/journey/launch",
752 "/shared/product-os/motion/load",
753 "/shared/product-os/motion/launch",
754 "/shared/avatar/:type",
755 "/shared/try/journey/designer"
756 ]
757 },
758 "assets/special": {
759 "identifier": "assets/special",
760 "paths": [
761 "/js/product.client.bundle.js",
762 "/js/product.designer.bundle.js",
763 "/ui/\*",
764 "/motion/\*",
765 "/.well-known/\*"
766 ]
767 },
768 "assets": {
769 "identifier": "assets",
770 "paths": [
771 "/js/\*",
772 "/css/\*",
773 "/images/\*",
774 "/webfonts/\*",
775 "/assets/\*",
776 "/favicon.ico",
777 "/robots.txt"
778 ]
779 },
780 "pipes/rest": {
781 "identifier": "pipes/rest",
782 "paths": [
783 "/api/plugins/rest/:pipe*"
784 ]
785 },
786 "pipes/stream": {
787 "identifier": "pipes/stream",
788 "paths": [
789 "/api/plugins/stream/:pipe"
790 ]
791 },
792 "pipes/graphql": {
793 "identifier": "pipes/graphql",
794 "paths": [
795 "/api/plugins/graphql/:pipe*"
796 ]
797 },
798 "pipes/ws": {
799 "identifier": "pipes/ws",
800 "paths": [
801 "/api/plugins/ws/:pipe"
802 ]
803 },
804 "pipes/mqtt": {
805 "identifier": "pipes/mqtt",
806 "paths": [
807 "/api/plugins/mqtt/:pipe"
808 ]
809 },
810 "channels/primus": {
811 "identifier": "channels/primus",
812 "paths": [
813 "/channels/product-os/primus*",
814 "/channels/product-os/primus/omega/supreme*",
815 "/channels/live/send",
816 "/channels/live/notify"
817 ]
818 },
819 "content": {
820 "identifier": "content",
821 "paths": [
822 "/error/:code",
823 "/",
824 "/\*"
825 ]
826 },
827 "swagger/api": {
828 "identifier": "swagger/api",
829 "paths": [
830 "/help/api/reference/openapi.json"
831 ]
832 },
833 "swagger/docs": {
834 "identifier": "swagger/docs",
835 "paths": [
836 "/help/api/reference/"
837 ]
838 },
839 "qrcode": {
840 "identifier": "qrcode",
841 "paths": [
842 "/api/qrcode/generate"
843 ]
844 },
845 "connect": {
846 "identifier": "connect",
847 "paths": [
848 "/api/connect/verify/:app",
849 "/api/connect/verify-complete/:app",
850 "/api/connect/authorize",
851 "/api/connect/request/:appID/:key/:action"
852 ]
853 },
854 "embedded": {
855 "identifier": "embedded",
856 "paths": [
857 "/api/embedded/:type/:action"
858 ]
859 },
860 "webhooks": {
861 "identifier": "webhooks",
862 "paths": [
863 "/api/hooks/store/:uuid",
864 "/api/hooks/subscribe",
865 "/api/hooks/unsubscribe/:key",
866 "/api/hooks/external/:key"
867 ]
868 },
869 "zapier": {
870 "identifier": "zapier",
871 "paths": [
872 "/api/hooks/zapier/send/:method/:type/:key",
873 "/api/hooks/zapier/send/:type"
874 ]
875 },
876 "ifttt": {
877 "identifier": "ifttt",
878 "paths": [
879 "/api/ifttt/status",
880 "/api/ifttt/test/setup",
881 "/api/ifttt/user/info",
882 "/api/ifttt/triggers/effect",
883 "/api/ifttt/actions/event",
884 "/api/hooks/ifttt/send/:type/:key"
885 ]
886 },
887 "msflow": {
888 "identifier": "msflow",
889 "paths": [
890 "/api/hooks/msflow/send/:type/:key"
891 ]
892 },
893 "integromat": {
894 "identifier": "integromat",
895 "paths": [
896 "/api/hooks/integromat/send/:type/:key"
897 ]
898 },
899 "notify": {
900 "identifier": "notify",
901 "paths": [
902 "/api/notify/:service/:type",
903 "/api/notifications/process",
904 "/api/notifications/email/process"
905 ]
906 },
907 "channels/telegram": {
908 "identifier": "channels/telegram",
909 "paths": [
910 "/channels/telegram/:uuid",
911 "/channels/telegram"
912 ]
913 },
914 "channels/facebook/messenger": {
915 "identifier": "channels/facebook/messenger",
916 "paths": [
917 "/channels/facebook/messenger"
918 ]
919 },
920 "channels/whatsapp": {
921 "identifier": "channels/whatsapp",
922 "paths": [
923 "/channels/whatsapp/whatsapp",
924 "/channels/whatsapp/connect",
925 "/channels/whatsapp/reconnect",
926 "/channels/whatsapp/disconnect",
927 "/channels/whatsapp/qr-refresh",
928 "/channels/whatsapp/connect-complete"
929 ]
930 },
931 "channels/line": {
932 "identifier": "channels/line",
933 "paths": [
934 "/channels/line/:uuid",
935 "/channels/line"
936 ]
937 },
938 "channels/viber": {
939 "identifier": "channels/viber",
940 "paths": [
941 "/channels/viber/:uuid",
942 "/channels/viber"
943 ]
944 },
945 "channels/twilio": {
946 "identifier": "channels/twilio",
947 "paths": [
948 "/channels/twilio/sms/:uuid",
949 "/channels/twilio/whatsapp/:uuid",
950 "/channels/twilio"
951 ]
952 },
953 "channels/wechat": {
954 "identifier": "channels/wechat",
955 "paths": [
956 "/channels/wechat/:uuid"
957 ]
958 },
959 "channels/slack": {
960 "identifier": "channels/slack",
961 "paths": [
962 "/channels/slack",
963 "/channels/slack/disconnect-all"
964 ]
965 },
966 "channels/microsoft/azurebot": {
967 "identifier": "channels/microsoft/azurebot",
968 "paths": [
969 "/channels/microsoft/azurebot/:uuid"
970 ]
971 },
972 "channels/dialogflow": {
973 "identifier": "channels/dialogflow",
974 "paths": [
975 "/channels/google/dialogflow/:uuid",
976 "/channels/google/dialogflow"
977 ]
978 },
979 "channels/amazon/alexa": {
980 "identifier": "channels/amazon/alexa",
981 "paths": [
982 "/channels/amazon/alexa/:uuid",
983 "/channels/amazon/alexa"
984 ]
985 },
986 "channels/intercom": {
987 "identifier": "channels/intercom",
988 "paths": [
989 "/channels/intercom"
990 ]
991 },
992 "channels/signal": {
993 "identifier": "channels/signal",
994 "paths": [
995 "/channels/signal/signal",
996 "/channels/signal/connect",
997 "/channels/signal/disconnect"
998 ]
999 },
1000 "channels/facebook/instagram": {
1001 "identifier": "channels/facebook/instagram",
1002 "paths": [
1003 "/channels/instagram/instagram",
1004 "/channels/instagram/connect",
1005 "/channels/instagram/disconnect"
1006 ]
1007 },
1008 "channels/product-os/rest": {
1009 "identifier": "channels/product-os/rest",
1010 "paths": [
1011 "/channels/product-os/rest/:mode"
1012 ]
1013 },
1014 "channels/product-os/ws": {
1015 "identifier": "channels/product-os/ws",
1016 "paths": [
1017 "/channels/product-os/ws"
1018 ]
1019 }
1020 },
1021 "remoteServices": {
1022 "stories": {
1023 "identifier": "stories",
1024 "path": "/stories"
1025 },
1026 "logging": {
1027 "identifier": "logging",
1028 "path": "/logging"
1029 },
1030 "users": {
1031 "identifier": "users",
1032 "path": "/users"
1033 },
1034 "oidc": {
1035 "identifier": "oidc",
1036 "path": "/oidc"
1037 },
1038 "manage-users": {
1039 "identifier": "manage-users",
1040 "path": "/manage-users"
1041 },
1042 "teams": {
1043 "identifier": "teams",
1044 "path": "/teams"
1045 },
1046 "motions": {
1047 "identifier": "motions",
1048 "path": "/motions"
1049 },
1050 "subscriptions": {
1051 "identifier": "subscriptions",
1052 "path": "/subscriptions"
1053 },
1054 "watchers": {
1055 "identifier": "watchers",
1056 "path": "/watchers"
1057 },
1058 "hooks": {
1059 "identifier": "hooks",
1060 "path": "/hooks"
1061 },
1062 "licences": {
1063 "identifier": "licences",
1064 "path": "/licences"
1065 },
1066 "notifications": {
1067 "identifier": "notifications",
1068 "path": "/notifications"
1069 },
1070 "invites": {
1071 "identifier": "invites",
1072 "path": "/invites"
1073 },
1074 "statistics": {
1075 "identifier": "statistics",
1076 "path": "/statistics"
1077 },
1078 "iplocations": {
1079 "identifier": "iplocations",
1080 "path": "/iplocations"
1081 },
1082 "templates": {
1083 "identifier": "templates",
1084 "path": "/templates"
1085 },
1086 "signals": {
1087 "identifier": "signals",
1088 "path": "/signals"
1089 },
1090 "plans": {
1091 "identifier": "plans",
1092 "path": "/plans"
1093 },
1094 "errors": {
1095 "identifier": "errors",
1096 "path": "/errors"
1097 },
1098 "queues": {
1099 "identifier": "queues",
1100 "path": "/queues"
1101 },
1102 "trainer": {
1103 "identifier": "trainer",
1104 "path": "/trainer"
1105 },
1106 "find-experiences": {
1107 "identifier": "find-experiences",
1108 "path": "/find-experiences"
1109 },
1110 "find-motions": {
1111 "identifier": "find-motions",
1112 "path": "/find-motions"
1113 },
1114 "experiences": {
1115 "identifier": "experiences",
1116 "path": "/experiences"
1117 },
1118 "journeys": {
1119 "identifier": "journeys",
1120 "path": "/journeys"
1121 },
1122 "liveexperiences": {
1123 "identifier": "liveexperiences",
1124 "path": "/liveexperiences"
1125 },
1126 "pages": {
1127 "identifier": "pages",
1128 "path": "/pages"
1129 },
1130 "reactions": {
1131 "identifier": "reactions",
1132 "path": "/reactions"
1133 },
1134 "tinyurls": {
1135 "identifier": "tinyurls",
1136 "path": "/tinyurls"
1137 },
1138 "media": {
1139 "identifier": "media",
1140 "path": "/media"
1141 },
1142 "connections": {
1143 "identifier": "connections",
1144 "path": "/connections"
1145 },
1146 "mappings": {
1147 "identifier": "mappings",
1148 "path": "/mappings"
1149 },
1150 "pipes": {
1151 "identifier": "pipes",
1152 "path": "/pipes"
1153 },
1154 "anatomies": {
1155 "identifier": "anatomies",
1156 "path": "/anatomies"
1157 }
1158 }
1159}
1160 */