solid-logic 1.3.17-9d25ceb7 → 1.3.17-a849582e

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 (114) hide show
  1. package/lib/acl/aclLogic.d.ts +12 -30
  2. package/lib/acl/aclLogic.d.ts.map +1 -1
  3. package/lib/acl/aclLogic.js +151 -116
  4. package/lib/acl/aclLogic.js.map +1 -1
  5. package/lib/authn/SolidAuthnLogic.d.ts.map +1 -1
  6. package/lib/authn/SolidAuthnLogic.js +2 -2
  7. package/lib/authn/SolidAuthnLogic.js.map +1 -1
  8. package/lib/chat/chatLogic.d.ts +16 -0
  9. package/lib/chat/chatLogic.d.ts.map +1 -0
  10. package/lib/chat/{ChatLogic.js → chatLogic.js} +82 -87
  11. package/lib/chat/chatLogic.js.map +1 -0
  12. package/lib/discovery/discoveryLogic.d.ts +8 -8
  13. package/lib/discovery/discoveryLogic.d.ts.map +1 -1
  14. package/lib/discovery/discoveryLogic.js +65 -72
  15. package/lib/discovery/discoveryLogic.js.map +1 -1
  16. package/lib/inbox/inboxLogic.d.ts +7 -0
  17. package/lib/inbox/inboxLogic.d.ts.map +1 -0
  18. package/lib/inbox/{InboxLogic.js → inboxLogic.js} +58 -64
  19. package/lib/inbox/inboxLogic.js.map +1 -0
  20. package/lib/index.d.ts +10 -13
  21. package/lib/index.d.ts.map +1 -1
  22. package/lib/index.js +76 -32
  23. package/lib/index.js.map +1 -1
  24. package/lib/logic/CustomError.d.ts +4 -0
  25. package/lib/logic/CustomError.d.ts.map +1 -1
  26. package/lib/logic/CustomError.js +17 -1
  27. package/lib/logic/CustomError.js.map +1 -1
  28. package/lib/logic/solidLogicSingleton.d.ts +35 -3
  29. package/lib/logic/solidLogicSingleton.d.ts.map +1 -1
  30. package/lib/logic/solidLogicSingleton.js +87 -8
  31. package/lib/logic/solidLogicSingleton.js.map +1 -1
  32. package/lib/profile/profileLogic.d.ts +13 -0
  33. package/lib/profile/profileLogic.d.ts.map +1 -0
  34. package/lib/profile/profileLogic.js +268 -0
  35. package/lib/profile/profileLogic.js.map +1 -0
  36. package/lib/typeIndex/typeIndexLogic.d.ts +31 -21
  37. package/lib/typeIndex/typeIndexLogic.d.ts.map +1 -1
  38. package/lib/typeIndex/typeIndexLogic.js +650 -295
  39. package/lib/typeIndex/typeIndexLogic.js.map +1 -1
  40. package/lib/types.d.ts +17 -0
  41. package/lib/types.d.ts.map +1 -1
  42. package/lib/util/containerLogic.d.ts +11 -0
  43. package/lib/util/containerLogic.d.ts.map +1 -0
  44. package/lib/{profile/ProfileLogic.js → util/containerLogic.js} +53 -44
  45. package/lib/util/containerLogic.js.map +1 -0
  46. package/lib/util/utilityLogic.d.ts +15 -0
  47. package/lib/util/utilityLogic.d.ts.map +1 -0
  48. package/lib/util/utilityLogic.js +272 -0
  49. package/lib/util/utilityLogic.js.map +1 -0
  50. package/lib/util/utils.d.ts +8 -0
  51. package/lib/util/utils.d.ts.map +1 -0
  52. package/lib/util/utils.js +48 -0
  53. package/lib/util/utils.js.map +1 -0
  54. package/package.json +3 -1
  55. package/src/acl/aclLogic.ts +135 -119
  56. package/src/authn/SolidAuthnLogic.ts +3 -2
  57. package/src/chat/chatLogic.ts +225 -0
  58. package/src/discovery/discoveryLogic.ts +66 -87
  59. package/src/inbox/inboxLogic.ts +57 -0
  60. package/src/index.ts +74 -21
  61. package/src/logic/CustomError.ts +5 -1
  62. package/src/logic/solidLogicSingleton.ts +160 -7
  63. package/src/profile/profileLogic.ts +134 -0
  64. package/src/typeIndex/typeIndexLogic.ts +417 -153
  65. package/src/types.ts +7 -3
  66. package/src/util/containerLogic.ts +54 -0
  67. package/src/util/ns.js +5 -0
  68. package/src/util/utilityLogic.ts +155 -0
  69. package/src/util/utils.ts +52 -0
  70. package/test/aclLogic.test.ts +13 -4
  71. package/test/chatLogic.test.ts +70 -71
  72. package/test/container.test.ts +56 -0
  73. package/test/discoveryLogic.test.ts +14 -14
  74. package/test/helpers/dataSetup.ts +134 -0
  75. package/test/helpers/setup.ts +4 -0
  76. package/test/inboxLogic.test.ts +39 -38
  77. package/test/logic.test.ts +11 -9
  78. package/test/profileLogic.test.ts +246 -0
  79. package/test/typeIndexLogic.test.ts +49 -22
  80. package/test/typeIndexLogicPart2.test.ts +485 -0
  81. package/test/utilityLogic.test.ts +172 -126
  82. package/test/utils.test.ts +32 -0
  83. package/lib/chat/ChatLogic.d.ts +0 -26
  84. package/lib/chat/ChatLogic.d.ts.map +0 -1
  85. package/lib/chat/ChatLogic.js.map +0 -1
  86. package/lib/chat/determineChatContainer.d.ts +0 -3
  87. package/lib/chat/determineChatContainer.d.ts.map +0 -1
  88. package/lib/chat/determineChatContainer.js +0 -12
  89. package/lib/chat/determineChatContainer.js.map +0 -1
  90. package/lib/inbox/InboxLogic.d.ts +0 -18
  91. package/lib/inbox/InboxLogic.d.ts.map +0 -1
  92. package/lib/inbox/InboxLogic.js.map +0 -1
  93. package/lib/logic/SolidLogic.d.ts +0 -48
  94. package/lib/logic/SolidLogic.d.ts.map +0 -1
  95. package/lib/logic/SolidLogic.js +0 -321
  96. package/lib/logic/SolidLogic.js.map +0 -1
  97. package/lib/profile/ProfileLogic.d.ts +0 -13
  98. package/lib/profile/ProfileLogic.d.ts.map +0 -1
  99. package/lib/profile/ProfileLogic.js.map +0 -1
  100. package/lib/util/UtilityLogic.d.ts +0 -33
  101. package/lib/util/UtilityLogic.d.ts.map +0 -1
  102. package/lib/util/UtilityLogic.js +0 -240
  103. package/lib/util/UtilityLogic.js.map +0 -1
  104. package/lib/util/uri.d.ts +0 -3
  105. package/lib/util/uri.d.ts.map +0 -1
  106. package/lib/util/uri.js +0 -9
  107. package/lib/util/uri.js.map +0 -1
  108. package/src/chat/ChatLogic.ts +0 -244
  109. package/src/chat/determineChatContainer.ts +0 -14
  110. package/src/inbox/InboxLogic.ts +0 -66
  111. package/src/logic/SolidLogic.ts +0 -262
  112. package/src/profile/ProfileLogic.ts +0 -44
  113. package/src/util/UtilityLogic.ts +0 -161
  114. package/src/util/uri.ts +0 -5
@@ -1,137 +1,153 @@
1
- /**
2
- * Simple Access Control
3
- *
4
- * This function sets up a simple default ACL for a resource, with
5
- * RWC for the owner, and a specified access (default none) for the public.
6
- * In all cases owner has read write control.
7
- * Parameter lists modes allowed to public
8
- *
9
- * @param options
10
- * @param options.public eg ['Read', 'Write']
11
- *
12
- * @returns Resolves with aclDoc uri on successful write
13
- */
1
+ import { graph, NamedNode, Namespace, serialize, sym } from "rdflib"
2
+ import ns from '../util/ns'
14
3
 
15
- import { graph, NamedNode, Namespace, serialize } from "rdflib"
16
- import solidNamespace from 'solid-namespace'
17
- import * as $rdf from 'rdflib'
18
- import { solidLogicSingleton } from "../logic/solidLogicSingleton"
19
- import { ACL_LINK } from "../util/UtilityLogic"
20
4
 
21
- export const ns = solidNamespace($rdf)
5
+ export const ACL_LINK = sym(
6
+ "http://www.iana.org/assignments/link-relations/acl"
7
+ );
22
8
 
23
- export function setACLUserPublic (
24
- docURI: string,
25
- me: NamedNode,
26
- options: {
27
- defaultForNew?: boolean,
28
- public?: []
29
- }
30
- ): Promise<NamedNode> {
31
- const aclDoc = solidLogicSingleton.store.any(
32
- solidLogicSingleton.store.sym(docURI),
33
- ACL_LINK
34
- )
35
-
36
- return Promise.resolve()
37
- .then(() => {
38
- if (aclDoc) {
39
- return aclDoc as NamedNode
9
+ export function createAclLogic(store) {
10
+
11
+ async function findAclDocUrl(url: string) {
12
+ const doc = store.sym(url);
13
+ await store.fetcher.load(doc);
14
+ const docNode = store.any(doc, ACL_LINK);
15
+ if (!docNode) {
16
+ throw new Error(`No ACL link discovered for ${url}`);
17
+ }
18
+ return docNode.value;
40
19
  }
41
-
42
- return fetchACLRel(docURI).catch(err => {
43
- throw new Error(`Error fetching rel=ACL header for ${docURI}: ${err}`)
44
- })
45
- })
46
- .then(aclDoc => {
47
- const aclText = genACLText(docURI, me, aclDoc.uri, options)
48
- if (!solidLogicSingleton.store.fetcher) {
49
- throw new Error('Cannot PUT this, store has no fetcher')
20
+ /**
21
+ * Simple Access Control
22
+ *
23
+ * This function sets up a simple default ACL for a resource, with
24
+ * RWC for the owner, and a specified access (default none) for the public.
25
+ * In all cases owner has read write control.
26
+ * Parameter lists modes allowed to public
27
+ *
28
+ * @param options
29
+ * @param options.public eg ['Read', 'Write']
30
+ *
31
+ * @returns Resolves with aclDoc uri on successful write
32
+ */
33
+ function setACLUserPublic (
34
+ docURI: string,
35
+ me: NamedNode,
36
+ options: {
37
+ defaultForNew?: boolean,
38
+ public?: []
50
39
  }
51
- return solidLogicSingleton.store.fetcher
52
- .webOperation('PUT', aclDoc.uri, {
53
- data: aclText,
54
- contentType: 'text/turtle'
55
- })
56
- .then(result => {
57
- if (!result.ok) {
58
- throw new Error('Error writing ACL text: ' + result.error)
40
+ ): Promise<NamedNode> {
41
+ const aclDoc = store.any(
42
+ store.sym(docURI),
43
+ ACL_LINK
44
+ )
45
+
46
+ return Promise.resolve()
47
+ .then(() => {
48
+ if (aclDoc) {
49
+ return aclDoc as NamedNode
59
50
  }
60
51
 
61
- return aclDoc
52
+ return fetchACLRel(docURI).catch(err => {
53
+ throw new Error(`Error fetching rel=ACL header for ${docURI}: ${err}`)
54
+ })
62
55
  })
63
- })
64
- }
56
+ .then(aclDoc => {
57
+ const aclText = genACLText(docURI, me, aclDoc.uri, options)
58
+ if (!store.fetcher) {
59
+ throw new Error('Cannot PUT this, store has no fetcher')
60
+ }
61
+ return store.fetcher
62
+ .webOperation('PUT', aclDoc.uri, {
63
+ data: aclText,
64
+ contentType: 'text/turtle'
65
+ })
66
+ .then(result => {
67
+ if (!result.ok) {
68
+ throw new Error('Error writing ACL text: ' + result.error)
69
+ }
65
70
 
66
- /**
67
- * @param docURI
68
- * @returns
69
- */
70
- function fetchACLRel (docURI: string): Promise<NamedNode> {
71
- const fetcher = solidLogicSingleton.store.fetcher
72
- if (!fetcher) {
73
- throw new Error('Cannot fetch ACL rel, store has no fetcher')
71
+ return aclDoc
72
+ })
73
+ })
74
74
  }
75
75
 
76
- return fetcher.load(docURI).then(result => {
77
- if (!result.ok) {
78
- throw new Error('fetchACLRel: While loading:' + (result as any).error)
76
+ /**
77
+ * @param docURI
78
+ * @returns
79
+ */
80
+ function fetchACLRel (docURI: string): Promise<NamedNode> {
81
+ const fetcher = store.fetcher
82
+ if (!fetcher) {
83
+ throw new Error('Cannot fetch ACL rel, store has no fetcher')
79
84
  }
80
85
 
81
- const aclDoc = solidLogicSingleton.store.any(
82
- solidLogicSingleton.store.sym(docURI),
83
- ACL_LINK
84
- )
86
+ return fetcher.load(docURI).then(result => {
87
+ if (!result.ok) {
88
+ throw new Error('fetchACLRel: While loading:' + (result as any).error)
89
+ }
85
90
 
86
- if (!aclDoc) {
87
- throw new Error('fetchACLRel: No Link rel=ACL header for ' + docURI)
88
- }
91
+ const aclDoc = store.any(
92
+ store.sym(docURI),
93
+ ACL_LINK
94
+ )
89
95
 
90
- return aclDoc as NamedNode
91
- })
92
- }
96
+ if (!aclDoc) {
97
+ throw new Error('fetchACLRel: No Link rel=ACL header for ' + docURI)
98
+ }
99
+
100
+ return aclDoc as NamedNode
101
+ })
102
+ }
93
103
 
94
- /**
95
- * @param docURI
96
- * @param me
97
- * @param aclURI
98
- * @param options
99
- *
100
- * @returns Serialized ACL
101
- */
102
- export function genACLText (
103
- docURI: string,
104
- me: NamedNode,
105
- aclURI: string,
106
- options: {
107
- defaultForNew?: boolean,
108
- public?: []
109
- } = {}
110
- ): string | undefined {
111
- const optPublic = options.public || []
112
- const g = graph()
113
- const auth = Namespace('http://www.w3.org/ns/auth/acl#')
114
- let a = g.sym(`${aclURI}#a1`)
115
- const acl = g.sym(aclURI)
116
- const doc = g.sym(docURI)
117
- g.add(a, ns.rdf('type'), auth('Authorization'), acl)
118
- g.add(a, auth('accessTo'), doc, acl)
119
- if (options.defaultForNew) {
120
- g.add(a, auth('default'), doc, acl)
121
- }
122
- g.add(a, auth('agent'), me, acl)
123
- g.add(a, auth('mode'), auth('Read'), acl)
124
- g.add(a, auth('mode'), auth('Write'), acl)
125
- g.add(a, auth('mode'), auth('Control'), acl)
104
+ /**
105
+ * @param docURI
106
+ * @param me
107
+ * @param aclURI
108
+ * @param options
109
+ *
110
+ * @returns Serialized ACL
111
+ */
112
+ function genACLText (
113
+ docURI: string,
114
+ me: NamedNode,
115
+ aclURI: string,
116
+ options: {
117
+ defaultForNew?: boolean,
118
+ public?: []
119
+ } = {}
120
+ ): string | undefined {
121
+ const optPublic = options.public || []
122
+ const g = graph()
123
+ const auth = Namespace('http://www.w3.org/ns/auth/acl#')
124
+ let a = g.sym(`${aclURI}#a1`)
125
+ const acl = g.sym(aclURI)
126
+ const doc = g.sym(docURI)
127
+ g.add(a, ns.rdf('type'), auth('Authorization'), acl)
128
+ g.add(a, auth('accessTo'), doc, acl)
129
+ if (options.defaultForNew) {
130
+ g.add(a, auth('default'), doc, acl)
131
+ }
132
+ g.add(a, auth('agent'), me, acl)
133
+ g.add(a, auth('mode'), auth('Read'), acl)
134
+ g.add(a, auth('mode'), auth('Write'), acl)
135
+ g.add(a, auth('mode'), auth('Control'), acl)
126
136
 
127
- if (optPublic.length) {
128
- a = g.sym(`${aclURI}#a2`)
129
- g.add(a, ns.rdf('type'), auth('Authorization'), acl)
130
- g.add(a, auth('accessTo'), doc, acl)
131
- g.add(a, auth('agentClass'), ns.foaf('Agent'), acl)
132
- for (let p = 0; p < optPublic.length; p++) {
133
- g.add(a, auth('mode'), auth(optPublic[p]), acl) // Like 'Read' etc
137
+ if (optPublic.length) {
138
+ a = g.sym(`${aclURI}#a2`)
139
+ g.add(a, ns.rdf('type'), auth('Authorization'), acl)
140
+ g.add(a, auth('accessTo'), doc, acl)
141
+ g.add(a, auth('agentClass'), ns.foaf('Agent'), acl)
142
+ for (let p = 0; p < optPublic.length; p++) {
143
+ g.add(a, auth('mode'), auth(optPublic[p]), acl) // Like 'Read' etc
144
+ }
145
+ }
146
+ return serialize(acl, g, aclURI)
147
+ }
148
+ return {
149
+ findAclDocUrl,
150
+ setACLUserPublic,
151
+ genACLText
134
152
  }
135
- }
136
- return serialize(acl, g, aclURI)
137
- }
153
+ }
@@ -3,6 +3,7 @@ import { appContext, offlineTestID } from "./authUtil";
3
3
  import * as debug from '../util/debug'
4
4
  import { Session } from "@inrupt/solid-client-authn-browser";
5
5
  import { AuthenticationContext, AuthnLogic } from "../types";
6
+
6
7
  export class SolidAuthnLogic implements AuthnLogic {
7
8
  private session: Session;
8
9
 
@@ -58,10 +59,10 @@ export class SolidAuthnLogic implements AuthnLogic {
58
59
  const curUrl = new URL(window.location.href)
59
60
  if (curUrl.hash !== postLoginRedirectHash) {
60
61
  if (history.pushState) {
61
- // console.log('Setting window.location.has using pushState')
62
+ // debug.log('Setting window.location.has using pushState')
62
63
  history.pushState(null, document.title, postLoginRedirectHash)
63
64
  } else {
64
- // console.warn('Setting window.location.has using location.hash')
65
+ // debug.warn('Setting window.location.has using location.hash')
65
66
  location.hash = postLoginRedirectHash
66
67
  }
67
68
  curUrl.hash = postLoginRedirectHash
@@ -0,0 +1,225 @@
1
+ import { NamedNode, Node, st, term } from "rdflib"
2
+ import { CreatedPaneOptions, NewPaneOptions } from "../types"
3
+ import { determineChatContainer, newThing } from "../util/utils"
4
+ import { ns as namespace } from '../util/ns'
5
+
6
+ const CHAT_LOCATION_IN_CONTAINER = "index.ttl#this";
7
+
8
+ export function createChatLogic(store, profileLogic) {
9
+ const ns = namespace
10
+
11
+ async function setAcl(
12
+ chatContainer: NamedNode,
13
+ me: NamedNode,
14
+ invitee: NamedNode
15
+ ): Promise<void> {
16
+ // Some servers don't present a Link http response header
17
+ // if the container doesn't exist yet, so refetch the container
18
+ // now that it has been created:
19
+ await store.fetcher.load(chatContainer);
20
+
21
+ // FIXME: check the Why value on this quad:
22
+ const chatAclDoc = store.any(
23
+ chatContainer,
24
+ new NamedNode("http://www.iana.org/assignments/link-relations/acl")
25
+ );
26
+ if (!chatAclDoc) {
27
+ throw new Error("Chat ACL doc not found!");
28
+ }
29
+
30
+ const aclBody = `
31
+ @prefix acl: <http://www.w3.org/ns/auth/acl#>.
32
+ <#owner>
33
+ a acl:Authorization;
34
+ acl:agent <${me.value}>;
35
+ acl:accessTo <.>;
36
+ acl:default <.>;
37
+ acl:mode
38
+ acl:Read, acl:Write, acl:Control.
39
+ <#invitee>
40
+ a acl:Authorization;
41
+ acl:agent <${invitee.value}>;
42
+ acl:accessTo <.>;
43
+ acl:default <.>;
44
+ acl:mode
45
+ acl:Read, acl:Append.
46
+ `;
47
+ await store.fetcher.webOperation("PUT", chatAclDoc.value, {
48
+ data: aclBody,
49
+ contentType: "text/turtle",
50
+ });
51
+ }
52
+
53
+ async function addToPrivateTypeIndex(chatThing, me) {
54
+ // Add to private type index
55
+ const privateTypeIndex = store.any(
56
+ me,
57
+ ns.solid("privateTypeIndex")
58
+ ) as NamedNode | null;
59
+ if (!privateTypeIndex) {
60
+ throw new Error("Private type index not found!");
61
+ }
62
+ await store.fetcher.load(privateTypeIndex);
63
+ const reg = newThing(privateTypeIndex);
64
+ const ins = [
65
+ st(
66
+ reg,
67
+ ns.rdf("type"),
68
+ ns.solid("TypeRegistration"),
69
+ privateTypeIndex.doc()
70
+ ),
71
+ st(
72
+ reg,
73
+ ns.solid("forClass"),
74
+ ns.meeting("LongChat"),
75
+ privateTypeIndex.doc()
76
+ ),
77
+ st(reg, ns.solid("instance"), chatThing, privateTypeIndex.doc()),
78
+ ];
79
+ await new Promise((resolve, reject) => {
80
+ store.updater.update([], ins, function (_uri, ok, errm) {
81
+ if (!ok) {
82
+ reject(new Error(errm));
83
+ } else {
84
+ resolve(null);
85
+ }
86
+ });
87
+ });
88
+ }
89
+
90
+ async function findChat(invitee: NamedNode) {
91
+ const me = await profileLogic.loadMe();
92
+ const podRoot = await profileLogic.getPodRoot(me);
93
+ const chatContainer = determineChatContainer(invitee, podRoot);
94
+ let exists = true;
95
+ try {
96
+ await store.fetcher.load(
97
+ new NamedNode(chatContainer.value + "index.ttl#this")
98
+ );
99
+ } catch (e) {
100
+ exists = false;
101
+ }
102
+ return { me, chatContainer, exists };
103
+ }
104
+
105
+ async function createChatThing(
106
+ chatContainer: NamedNode,
107
+ me: NamedNode
108
+ ): Promise<NamedNode> {
109
+ const created = await mintNew({
110
+ me,
111
+ newBase: chatContainer.value,
112
+ });
113
+ return created.newInstance;
114
+ }
115
+
116
+ function mintNew(newPaneOptions: NewPaneOptions): Promise<CreatedPaneOptions> {
117
+ const kb = store;
118
+ const updater = kb.updater;
119
+ if (newPaneOptions.me && !newPaneOptions.me.uri) {
120
+ throw new Error("chat mintNew: Invalid userid " + newPaneOptions.me);
121
+ }
122
+
123
+ const newInstance = (newPaneOptions.newInstance =
124
+ newPaneOptions.newInstance ||
125
+ kb.sym(newPaneOptions.newBase + CHAT_LOCATION_IN_CONTAINER));
126
+ const newChatDoc = newInstance.doc();
127
+
128
+ kb.add(
129
+ newInstance,
130
+ ns.rdf("type"),
131
+ ns.meeting("LongChat"),
132
+ newChatDoc
133
+ );
134
+ kb.add(newInstance, ns.dc("title"), "Chat channel", newChatDoc);
135
+ kb.add(
136
+ newInstance,
137
+ ns.dc("created"),
138
+ term<Node>(new Date(Date.now())),
139
+ newChatDoc
140
+ );
141
+ if (newPaneOptions.me) {
142
+ kb.add(newInstance, ns.dc("author"), newPaneOptions.me, newChatDoc);
143
+ }
144
+
145
+ return new Promise(function (resolve, reject) {
146
+ updater?.put(
147
+ newChatDoc,
148
+ kb.statementsMatching(undefined, undefined, undefined, newChatDoc),
149
+ "text/turtle",
150
+ function (uri2, ok, message) {
151
+ if (ok) {
152
+ resolve({
153
+ ...newPaneOptions,
154
+ newInstance,
155
+ });
156
+ } else {
157
+ reject(
158
+ new Error(
159
+ "FAILED to save new chat channel at: " + uri2 + " : " + message
160
+ )
161
+ );
162
+ }
163
+ }
164
+ );
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Find (and optionally create) an individual chat between the current user and the given invitee
170
+ * @param invitee - The person to chat with
171
+ * @param createIfMissing - Whether the chat should be created, if missing
172
+ * @returns null if missing, or a node referring to an already existing chat, or the newly created chat
173
+ */
174
+ async function getChat(
175
+ invitee: NamedNode,
176
+ createIfMissing = true
177
+ ): Promise<NamedNode | null> {
178
+ const { me, chatContainer, exists } = await findChat(invitee);
179
+ if (exists) {
180
+ return new NamedNode(chatContainer.value + CHAT_LOCATION_IN_CONTAINER);
181
+ }
182
+
183
+ if (createIfMissing) {
184
+ const chatThing = await createChatThing(chatContainer, me);
185
+ await sendInvite(invitee, chatThing);
186
+ await setAcl(chatContainer, me, invitee);
187
+ await addToPrivateTypeIndex(chatThing, me);
188
+ return chatThing;
189
+ }
190
+ return null;
191
+ }
192
+
193
+ async function sendInvite(invitee: NamedNode, chatThing: NamedNode) {
194
+ await store.fetcher.load(invitee.doc());
195
+ const inviteeInbox = store.any(
196
+ invitee,
197
+ ns.ldp("inbox"),
198
+ undefined,
199
+ invitee.doc()
200
+ );
201
+ if (!inviteeInbox) {
202
+ throw new Error(`Invitee inbox not found! ${invitee.value}`);
203
+ }
204
+ const inviteBody = `
205
+ <> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;
206
+ ${ns.rdf("seeAlso")} <${chatThing.value}> .
207
+ `;
208
+
209
+ const inviteResponse = await store.fetcher.webOperation(
210
+ "POST",
211
+ inviteeInbox.value,
212
+ {
213
+ data: inviteBody,
214
+ contentType: "text/turtle",
215
+ }
216
+ );
217
+ const locationStr = inviteResponse?.headers.get("location");
218
+ if (!locationStr) {
219
+ throw new Error(`Invite sending returned a ${inviteResponse?.status}`);
220
+ }
221
+ }
222
+ return {
223
+ setAcl, addToPrivateTypeIndex, findChat, createChatThing, getChat, sendInvite, mintNew
224
+ }
225
+ }