pyret-embed 0.0.19 → 0.0.21

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/dist/pyret.js CHANGED
@@ -1,279 +1,65 @@
1
- const CPO = "https://pyret-horizon.herokuapp.com/editor";
2
- const defaultOptions = {
3
- footerStyle: 'hide',
4
- warnOnExit: false,
5
- hideDefinitions: false,
6
- hideInteractions: false
7
- };
8
- const defaultConfig = {
9
- src: CPO,
10
- state: false,
11
- options: defaultOptions
12
- };
13
- function sendRpcResponse(frame, data, result) {
14
- frame.contentWindow.postMessage({
15
- protocol: 'pyret-rpc',
16
- data: {
17
- type: 'rpc-response',
18
- callbackId: data.callbackId,
19
- ...result
20
- }
21
- });
22
- }
23
- async function receiveRPC(frame, e, rpcs) {
24
- console.log("RPC:", e.data);
25
- const data = e.data.data;
26
- const module = rpcs[data.module];
27
- if (!module[data.method]) {
28
- sendRpcResponse(frame, data, { resultType: 'exception', exception: `Unknown method ${data.method}` });
29
- }
30
- else {
31
- try {
32
- const result = await module[data.method](...data.args);
33
- sendRpcResponse(frame, data, { resultType: 'value', result });
34
- }
35
- catch (exn) {
36
- sendRpcResponse(frame, data, { resultType: 'exception', exception: String(exn) });
37
- }
38
- return;
39
- }
40
- }
41
- export function makeEmbedConfig(config) {
42
- let mergedConfig = { ...defaultConfig, ...config };
43
- let mergedOptions = { ...defaultConfig.options, ...config.options };
44
- let { container, src } = mergedConfig;
45
- let id = config.id || ("pyret-embed" + Math.floor(Math.random() * 1000000));
46
- const hasprop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
47
- console.log("Pyret embed config:", mergedConfig, mergedOptions);
48
- const propIfTrue = (obj, prop) => { if (obj[prop] === true) {
49
- return `&${prop}=true`;
50
- }
51
- else {
52
- return "";
53
- } };
54
- const propIfPresent = (obj, prop) => { if (hasprop(obj, prop)) {
55
- return `&${prop}=${obj[prop]}`;
56
- }
57
- else {
58
- return "";
59
- } };
60
- const fragment = `${propIfPresent(mergedOptions, "footerStyle")}${propIfPresent(mergedOptions, "warnOnExit")}${propIfTrue(mergedOptions, "hideDefinitions")}${propIfTrue(mergedOptions, "hideInteractions")}`;
61
- if (src.indexOf("#") !== -1) {
62
- src = src + "&" + fragment;
63
- }
64
- else {
65
- src = src + "#" + fragment;
66
- }
67
- let messageNumber = 0;
68
- let currentState;
69
- function sendReset(frame, state) {
70
- if (!state) {
71
- state = {
72
- definitionsAtLastRun: false,
73
- interactionsSinceLastRun: [],
74
- editorContents: "use context starter2024",
75
- replContents: ""
76
- };
77
- }
78
- if (typeof state === "object") {
79
- state.messageNumber = 0;
80
- }
81
- currentState = state;
82
- const payload = {
83
- data: {
84
- type: 'reset',
85
- state: typeof state === "string" ? state : JSON.stringify(state)
86
- },
87
- protocol: 'pyret'
88
- };
89
- frame.contentWindow.postMessage(payload, '*');
90
- }
91
- function gainControl(frame) {
92
- frame.contentWindow.postMessage({
93
- type: 'gainControl'
94
- }, '*');
95
- }
96
- function setInteractions(frame, text) {
97
- messageNumber += 1;
98
- const change = {
99
- from: { line: 0, ch: 0 },
100
- to: { line: 0, ch: 0 },
101
- text: text
102
- };
103
- currentState = { ...currentState, messageNumber, replContents: text };
104
- const payload = {
105
- protocol: 'pyret',
106
- data: {
107
- type: 'changeRepl',
108
- change: change
109
- },
110
- state: currentState
111
- };
112
- frame.contentWindow.postMessage(payload, '*');
113
- }
114
- function runDefinitions(frame) {
115
- messageNumber += 1;
116
- currentState = { ...currentState, messageNumber, interactionsSinceLastRun: [], definitionsAtLastRun: currentState.editorContents };
117
- const payload = {
118
- protocol: 'pyret',
119
- data: {
120
- type: 'run'
121
- },
122
- state: currentState
123
- };
124
- frame.contentWindow.postMessage(payload, '*');
125
- }
126
- function clearInteractions(frame) {
127
- messageNumber += 1;
128
- const payload = {
129
- protocol: 'pyret',
130
- data: {
131
- type: 'clearInteractions'
132
- },
133
- state: currentState
134
- };
135
- frame.contentWindow.postMessage(payload, '*');
136
- }
137
- let resultCounter = 0;
138
- function runInteractionResult(frame) {
139
- const { promise, resolve, reject } = Promise.withResolvers();
140
- messageNumber += 1;
141
- const newInteractions = currentState.interactionsSinceLastRun.concat([currentState.replContents]);
142
- currentState = {
143
- ...currentState,
144
- messageNumber: messageNumber,
145
- interactionsSinceLastRun: newInteractions,
146
- replContents: "",
147
- };
148
- const payload = {
149
- protocol: 'pyret',
150
- data: {
151
- type: 'runInteraction',
152
- reportAnswer: 'interaction' + (++resultCounter)
153
- },
154
- state: currentState
155
- };
156
- frame.contentWindow.postMessage(payload, '*');
157
- window.addEventListener('message', message => {
158
- if (message.data.protocol !== 'pyret') {
159
- return;
160
- }
161
- if (message.source !== frame.contentWindow) {
162
- return;
163
- }
164
- const pyretMessage = message.data;
165
- if (pyretMessage.data.type === 'interactionResult') {
166
- resolve(pyretMessage.data.textResult);
167
- }
168
- });
169
- return promise;
170
- }
171
- function directPostMessage(frame, message) {
172
- frame.contentWindow.postMessage(message);
173
- }
174
- /* An issue we run into with iframes and scrolling is that CPO wants to scroll
175
- interactions around sometimes. However, scrolling elements in the iframe
176
- can scroll the outer page as well to focus it. We don't want that.
177
- We can prevent this by making the iframe position: fixed, but that makes
178
- sensible positioning hard. So we create a wrapper iframe that works in the
179
- normal flow, and make the actual CPO iframe inside that.
180
-
181
- Since CPO only knows how to postMessage to its immediate parent, we also
182
- proxy all requests through the wrapper, and that's what the client sees.
183
- */
184
- const wrapper = document.createElement("iframe");
185
- wrapper.style = "width: 100%; height: 100%; border: 0; display: block;";
186
- wrapper.srcdoc = `
187
- <html>
188
- <head>
189
- <style>
190
- html, body { height: 100%; }
191
- body { margin: 0; padding: 0; }
192
- </style>
193
- <script>
194
- window.addEventListener('message', (e) => {
195
- if (e.source === window.parent) {
196
- const iframes = document.getElementsByTagName("iframe");
197
- iframes[0].contentWindow.postMessage(e.data, "*");
198
- }
199
- else {
200
- window.parent.postMessage(e.data, "*");
201
- }
202
- });
203
- </script>
204
- <body></body>
205
- </html>`;
206
- container.appendChild(wrapper);
207
- wrapper.addEventListener("load", () => {
208
- const wrapperBody = wrapper.contentDocument.body;
209
- const inner = document.createElement("iframe");
210
- inner.src = src || CPO;
211
- inner.style = "width: 100%; height: 100%; border: 0; display: block; position: fixed;";
212
- inner.width = "100%";
213
- inner.id = id;
214
- inner.frameBorder = "0";
215
- wrapperBody.appendChild(inner);
216
- });
217
- const frame = wrapper;
218
- frame.id = id;
219
- const { promise, resolve, reject } = Promise.withResolvers();
220
- setTimeout(() => reject(new Error("Timeout waiting for Pyret to load")), 60000);
221
- const onChangeCallbacks = [];
222
- window.addEventListener('message', message => {
223
- if (message.data.protocol === 'pyret-rpc') {
224
- receiveRPC(frame, message, mergedConfig.rpc || {}).catch(exn => {
225
- console.error("Error in RPC handler:", exn);
226
- });
227
- return;
228
- }
229
- if (message.data.protocol !== 'pyret') {
230
- return;
231
- }
232
- if (message.source !== frame.contentWindow) {
233
- return;
234
- }
235
- const pyretMessage = message.data;
236
- const typ = pyretMessage.data.type;
237
- if (typ === 'pyret-init') {
238
- gainControl(frame);
239
- if (mergedConfig.state) {
240
- sendReset(frame, mergedConfig.state);
241
- }
242
- const api = makeEmbedAPI(frame);
243
- frame.pyretEmbed = api;
244
- resolve(api);
245
- }
246
- else if (typ === "changeRepl" || typ === "change") {
247
- onChangeCallbacks.forEach(cb => cb(pyretMessage));
248
- currentState = pyretMessage.state;
249
- }
250
- else {
251
- currentState = pyretMessage.state;
252
- }
253
- });
254
- function makeEmbedAPI(frame) {
255
- return {
256
- sendReset: (state) => sendReset(frame, state),
257
- postMessage: (message) => directPostMessage(frame, message),
258
- getFrame: () => frame,
259
- setInteractions: (text) => setInteractions(frame, text),
260
- runDefinitions: () => runDefinitions(frame),
261
- runInteractionResult: async () => await runInteractionResult(frame),
262
- onChange: (callback) => onChangeCallbacks.push(callback),
263
- clearInteractions: () => clearInteractions(frame),
264
- currentState: () => currentState,
265
- };
266
- }
267
- return promise;
268
- }
269
- export function makeEmbed(id, container, src) {
270
- const config = {
271
- container,
272
- id,
273
- options: {}
274
- };
275
- if (src) {
276
- config.src = src;
277
- }
278
- return makeEmbedConfig(config);
279
- }
1
+ /*
2
+ * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
3
+ * This devtool is neither made for production nor for readable output files.
4
+ * It uses "eval()" calls to create a separate source file in the browser devtools.
5
+ * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
6
+ * or disable the default devtool with "devtool: false".
7
+ * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
8
+ */
9
+ /******/ var __webpack_modules__ = ({
10
+
11
+ /***/ "./src/pyret.ts":
12
+ /*!**********************!*\
13
+ !*** ./src/pyret.ts ***!
14
+ \**********************/
15
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
16
+
17
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ makeEmbed: () => (/* binding */ makeEmbed),\n/* harmony export */ makeEmbedConfig: () => (/* binding */ makeEmbedConfig)\n/* harmony export */ });\nconst CPO = \"https://pyret-horizon.herokuapp.com/editor\";\nconst defaultOptions = {\n footerStyle: 'hide',\n warnOnExit: false,\n hideDefinitions: false,\n hideInteractions: false\n};\nconst defaultConfig = {\n src: CPO,\n state: false,\n options: defaultOptions\n};\nfunction sendRpcResponse(frame, data, result) {\n frame.contentWindow.postMessage({\n protocol: 'pyret-rpc',\n data: {\n type: 'rpc-response',\n callbackId: data.callbackId,\n ...result\n }\n });\n}\nasync function receiveRPC(frame, e, rpcs) {\n console.log(\"RPC:\", e.data);\n const data = e.data.data;\n const module = rpcs[data.module];\n if (!module[data.method]) {\n sendRpcResponse(frame, data, { resultType: 'exception', exception: `Unknown method ${data.method}` });\n }\n else {\n try {\n const result = await module[data.method](...data.args);\n sendRpcResponse(frame, data, { resultType: 'value', result });\n }\n catch (exn) {\n sendRpcResponse(frame, data, { resultType: 'exception', exception: String(exn) });\n }\n return;\n }\n}\nfunction makeEmbedConfig(config) {\n let mergedConfig = { ...defaultConfig, ...config };\n let mergedOptions = { ...defaultConfig.options, ...config.options };\n let { container, src } = mergedConfig;\n let id = config.id || (\"pyret-embed\" + Math.floor(Math.random() * 1000000));\n const hasprop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);\n console.log(\"Pyret embed config:\", mergedConfig, mergedOptions);\n const propIfTrue = (obj, prop) => { if (obj[prop] === true) {\n return `&${prop}=true`;\n }\n else {\n return \"\";\n } };\n const propIfPresent = (obj, prop) => { if (hasprop(obj, prop)) {\n return `&${prop}=${obj[prop]}`;\n }\n else {\n return \"\";\n } };\n const fragment = `${propIfPresent(mergedOptions, \"footerStyle\")}${propIfPresent(mergedOptions, \"warnOnExit\")}${propIfTrue(mergedOptions, \"hideDefinitions\")}${propIfTrue(mergedOptions, \"hideInteractions\")}`;\n if (src.indexOf(\"#\") !== -1) {\n src = src + \"&\" + fragment;\n }\n else {\n src = src + \"#\" + fragment;\n }\n let messageNumber = 0;\n let currentState;\n function sendReset(frame, state) {\n if (!state) {\n state = {\n definitionsAtLastRun: false,\n interactionsSinceLastRun: [],\n editorContents: \"use context starter2024\",\n replContents: \"\"\n };\n }\n if (typeof state === \"object\") {\n state.messageNumber = 0;\n }\n currentState = state;\n const payload = {\n data: {\n type: 'reset',\n state: typeof state === \"string\" ? state : JSON.stringify(state)\n },\n protocol: 'pyret'\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function gainControl(frame) {\n frame.contentWindow.postMessage({\n type: 'gainControl'\n }, '*');\n }\n function setInteractions(frame, text) {\n messageNumber += 1;\n const change = {\n from: { line: 0, ch: 0 },\n to: { line: 0, ch: 0 },\n text: text\n };\n currentState = { ...currentState, messageNumber, replContents: text };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'changeRepl',\n change: change\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function runDefinitions(frame) {\n messageNumber += 1;\n currentState = { ...currentState, messageNumber, interactionsSinceLastRun: [], definitionsAtLastRun: currentState.editorContents };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'run'\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function clearInteractions(frame) {\n messageNumber += 1;\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'clearInteractions'\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n let resultCounter = 0;\n function runInteractionResult(frame) {\n const { promise, resolve, reject } = Promise.withResolvers();\n messageNumber += 1;\n const newInteractions = currentState.interactionsSinceLastRun.concat([currentState.replContents]);\n currentState = {\n ...currentState,\n messageNumber: messageNumber,\n interactionsSinceLastRun: newInteractions,\n replContents: \"\",\n };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'runInteraction',\n reportAnswer: 'interaction' + (++resultCounter)\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n window.addEventListener('message', message => {\n if (message.data.protocol !== 'pyret') {\n return;\n }\n if (message.source !== frame.contentWindow) {\n return;\n }\n const pyretMessage = message.data;\n if (pyretMessage.data.type === 'interactionResult') {\n resolve(pyretMessage.data.textResult);\n }\n });\n return promise;\n }\n function directPostMessage(frame, message) {\n frame.contentWindow.postMessage(message);\n }\n /* An issue we run into with iframes and scrolling is that CPO wants to scroll\n interactions around sometimes. However, scrolling elements in the iframe\n can scroll the outer page as well to focus it. We don't want that.\n We can prevent this by making the iframe position: fixed, but that makes\n sensible positioning hard. So we create a wrapper iframe that works in the\n normal flow, and make the actual CPO iframe inside that.\n \n Since CPO only knows how to postMessage to its immediate parent, we also\n proxy all requests through the wrapper, and that's what the client sees.\n */\n const wrapper = document.createElement(\"iframe\");\n wrapper.style = \"width: 100%; height: 100%; border: 0; display: block;\";\n wrapper.srcdoc = `\n<html>\n<head>\n<style>\nhtml, body { height: 100%; }\nbody { margin: 0; padding: 0; }\n</style>\n<script>\nwindow.addEventListener('message', (e) => {\n if (e.source === window.parent) {\n const iframes = document.getElementsByTagName(\"iframe\");\n iframes[0].contentWindow.postMessage(e.data, \"*\");\n }\n else {\n window.parent.postMessage(e.data, \"*\");\n }\n});\n</script>\n<body></body>\n</html>`;\n container.appendChild(wrapper);\n wrapper.addEventListener(\"load\", () => {\n const wrapperBody = wrapper.contentDocument.body;\n const inner = document.createElement(\"iframe\");\n inner.src = src || CPO;\n inner.style = \"width: 100%; height: 100%; border: 0; display: block; position: fixed;\";\n inner.width = \"100%\";\n inner.id = id;\n inner.frameBorder = \"0\";\n wrapperBody.appendChild(inner);\n });\n const frame = wrapper;\n frame.id = id;\n const { promise, resolve, reject } = Promise.withResolvers();\n setTimeout(() => reject(new Error(\"Timeout waiting for Pyret to load\")), 60000);\n const onChangeCallbacks = [];\n window.addEventListener('message', message => {\n if (message.data.protocol === 'pyret-rpc') {\n receiveRPC(frame, message, mergedConfig.rpc || {}).catch(exn => {\n console.error(\"Error in RPC handler:\", exn);\n });\n return;\n }\n if (message.data.protocol !== 'pyret') {\n return;\n }\n if (message.source !== frame.contentWindow) {\n return;\n }\n const pyretMessage = message.data;\n const typ = pyretMessage.data.type;\n if (typ === 'pyret-init') {\n gainControl(frame);\n if (mergedConfig.state) {\n sendReset(frame, mergedConfig.state);\n }\n const api = makeEmbedAPI(frame);\n frame.pyretEmbed = api;\n resolve(api);\n }\n else if (typ === \"changeRepl\" || typ === \"change\") {\n onChangeCallbacks.forEach(cb => cb(pyretMessage));\n currentState = pyretMessage.state;\n }\n else {\n currentState = pyretMessage.state;\n }\n });\n function makeEmbedAPI(frame) {\n return {\n sendReset: (state) => sendReset(frame, state),\n postMessage: (message) => directPostMessage(frame, message),\n getFrame: () => frame,\n setInteractions: (text) => setInteractions(frame, text),\n runDefinitions: () => runDefinitions(frame),\n runInteractionResult: async () => await runInteractionResult(frame),\n onChange: (callback) => onChangeCallbacks.push(callback),\n clearInteractions: () => clearInteractions(frame),\n currentState: () => currentState,\n };\n }\n return promise;\n}\nfunction makeEmbed(id, container, src) {\n const config = {\n container,\n id,\n options: {}\n };\n if (src) {\n config.src = src;\n }\n return makeEmbedConfig(config);\n}\n\n\n//# sourceURL=webpack://pyret-embed/./src/pyret.ts?");
18
+
19
+ /***/ })
20
+
21
+ /******/ });
22
+ /************************************************************************/
23
+ /******/ // The require scope
24
+ /******/ var __webpack_require__ = {};
25
+ /******/
26
+ /************************************************************************/
27
+ /******/ /* webpack/runtime/define property getters */
28
+ /******/ (() => {
29
+ /******/ // define getter functions for harmony exports
30
+ /******/ __webpack_require__.d = (exports, definition) => {
31
+ /******/ for(var key in definition) {
32
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
33
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
34
+ /******/ }
35
+ /******/ }
36
+ /******/ };
37
+ /******/ })();
38
+ /******/
39
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
40
+ /******/ (() => {
41
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
42
+ /******/ })();
43
+ /******/
44
+ /******/ /* webpack/runtime/make namespace object */
45
+ /******/ (() => {
46
+ /******/ // define __esModule on exports
47
+ /******/ __webpack_require__.r = (exports) => {
48
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
49
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
50
+ /******/ }
51
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
52
+ /******/ };
53
+ /******/ })();
54
+ /******/
55
+ /************************************************************************/
56
+ /******/
57
+ /******/ // startup
58
+ /******/ // Load entry module and return exports
59
+ /******/ // This entry module can't be inlined because the eval devtool is used.
60
+ /******/ var __webpack_exports__ = {};
61
+ /******/ __webpack_modules__["./src/pyret.ts"](0, __webpack_exports__, __webpack_require__);
62
+ /******/ const __webpack_exports__makeEmbed = __webpack_exports__.makeEmbed;
63
+ /******/ const __webpack_exports__makeEmbedConfig = __webpack_exports__.makeEmbedConfig;
64
+ /******/ export { __webpack_exports__makeEmbed as makeEmbed, __webpack_exports__makeEmbedConfig as makeEmbedConfig };
65
+ /******/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyret-embed",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "A library for embedding Pyret into webpages",
5
5
  "main": "dist/pyret.js",
6
6
  "files": [
@@ -0,0 +1,32 @@
1
+ import * as myfs from 'fs';
2
+ /**
3
+ * This is a simple in-memory RPC implementation for file operations.
4
+ * It expects to have ZenFS be used as the browser-based fallback in webpack for
5
+ * `fs`.
6
+ *
7
+ * It allows errors to propagate to the caller; the RPC infrastructure should
8
+ * forward these on.
9
+ */
10
+ export declare const rpc: {
11
+ fs: {
12
+ readFile: (path: string) => Promise<NonSharedBuffer>;
13
+ writeFile: (path: string, data: string | Buffer) => Promise<void>;
14
+ exists: (path: string) => Promise<boolean>;
15
+ stat: (p: string) => Promise<{
16
+ mtime: number;
17
+ ctime: number;
18
+ size: number;
19
+ native: myfs.Stats;
20
+ }>;
21
+ createDir: (p: string) => Promise<void>;
22
+ };
23
+ path: {
24
+ join: (...paths: string[]) => any;
25
+ resolve: (p: string) => any;
26
+ basename: (p: string) => any;
27
+ dirname: (p: string) => any;
28
+ extname: (p: string) => any;
29
+ relative: (from: string, to: string) => any;
30
+ 'is-absolute': (p: string) => any;
31
+ };
32
+ };