You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
window.kolibri is installed for every sandboxed viewer but only answered for custom-presentation channels, so from an H5P or plain HTML5 node a call never settles. Where it does work the surface is broken: getContext() always resolves to {}, failures reject with undefined, and pagination leaks the backend's query vocabulary into an otherwise camelCase API. Replace it with a thin pass-through to the content API, and move both halves of the shim into the html5_viewer plugin.
Complexity: High Target branch: develop
Context
Kolibri is one of SandboxHandler.baseShims (packages/kolibri-sandbox/src/SandboxHandler.js:43). Its main side is kolibri/plugins/learn/frontend/views/ChannelRenderer/CustomContentRenderer.vue: 11 sandbox.on registrations, a local createReturnMsg, and sandbox.mediator.sendMessage replies.
getContext() replies data: {} unconditionally (CustomContentRenderer.vue:306), and its fallback — the context computed at :83-86 — is JSON.stringify(decodeURI(this.$route.query)), which yields "[object Object]".
createReturnMsg (:45-59) infers success from data truthiness, so a falsy-but-valid payload becomes FAILURE with err: null, and mediator.js:77 then calls bare reject().
more is documented as {cursor} but pagination.py:181-192 returns the whole query-param dict, so an app receives kind_in/descendant_of/max_results while calling with kinds/descendantOf/maxResults.
A SandboxedContentViewerHook builds two bundles (buildConfig.js): the plugin's main bundle, and a sandbox_handler bundle built with no Kolibri externals, served from the alt origin and script-loaded into the iframe. Main-side code needing ContentNodeResource cannot ship in the latter.
useSandbox returns the client as a shallowRef, re-exposed by SandboxedContentViewer, so a host can bind handlers to it.
The Change
window.kolibri becomes a thin pass-through to the content API, installed only for HTML5 zip content:
window.kolibri={
version,// string, from the init payloadcontentNode: {list(params),// GET /api/content/contentnode/retrieve(id,params),// GET /api/content/contentnode/<id>/tree(id,params),// GET /api/content/contentnode_tree/<id>/},navigateTo(nodeId),}
Params and responses should be the endpoint's own, untranslated in either direction. list resolves to { more, results, labels } and the next page is list(more). Queries are not confined to the calling node's channel.
self should be accepted anywhere a node id is — the id argument, or a param value such as parent or descendant_of — resolving to the calling node.
Rejections should be Error instances carrying the response status: propagated from the endpoint where there is one, 400 where the shim rejects a call before it leaves, 501 where the host binds no handler, 504 on a 30 second deadline. Destroying the shim rejects everything still outstanding.
The shim should move onto Html5ZipHandler beside SCORMShim, with its main half in the html5_viewer plugin's main bundle. The halves should talk on the shim's own kolibri namespace, which needs sendMessageAwaitReply on SandboxShim and a MainClient surface for attaching a main half, so neither the plugin nor the host reaches into the mediator.
CustomContentRenderer should become a thin wrapper that renders the viewer and binds navigateTo.
How to Get There
A custom channel is the only content exercising the API today: TopicsPage renders CustomContentRenderer when plugin_data.enableCustomChannelNav is set and the topic has options.modality === 'CUSTOM_NAVIGATION' (TopicsPage/index.vue:709-718).
The channel's root topic ships an HTML5 zip that calls window.kolibri from inside the sandbox iframe.
Acceptance Criteria
window.kolibri exposes version, contentNode.list, contentNode.retrieve, contentNode.tree and navigateTo, and nothing else.
Params and responses pass through untranslated in both directions, and list(more) returns the next page.
self resolves to the calling node both as an id argument and as a param value such as parent or descendant_of.
Failed calls reject with an Error carrying status: propagated from the endpoint, 400 for a call rejected before it leaves, 501 where the host binds no handler, 504 after a 30 second deadline. Destroying the shim rejects outstanding calls.
window.kolibri is installed for HTML5 zip content only — not for H5P or Bloom.
Requests go through SandboxShim.sendMessageAwaitReply and a MainClient attachment surface; neither CustomContentRenderer nor the html5_viewer main half touches a mediator directly.
getRandomNodes, getChannelFilterOptions, themeRenderer, updateContext and getContext are gone, together with fetchRandomCollection/_v2, fetchFilterOptions/_v2, the backend random and channel filter_options actions, validateChannelTheme, createReturnMsg, and the kolibri-only events in base.js.
Unit tests cover self substitution, status mapping, deadline expiry and teardown rejection.
Testing
pnpm test-jest --testPathPatterns kolibri-sandbox, plus the html5_viewer and learn plugin suites.
pytest kolibri/core/content/test/ for the endpoint removals.
Manually: an H5P and a Bloom resource still load with the shim no longer installed for them.
Drafted with Claude Code. Every claim about current behaviour was checked against the code rather than taken on trust — line references verified, and the orphaned call chains (random, filter_options, validateChannelTheme) confirmed by searching for callers rather than assumed. The API shape came out of a design conversation and the decisions are mine; Claude drafted the write-up, which I trimmed.
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
window.kolibriis installed for every sandboxed viewer but only answered for custom-presentation channels, so from an H5P or plain HTML5 node a call never settles. Where it does work the surface is broken:getContext()always resolves to{}, failures reject withundefined, and pagination leaks the backend's query vocabulary into an otherwise camelCase API. Replace it with a thin pass-through to the content API, and move both halves of the shim into thehtml5_viewerplugin.Complexity: High
Target branch: develop
Context
Kolibriis one ofSandboxHandler.baseShims(packages/kolibri-sandbox/src/SandboxHandler.js:43). Its main side iskolibri/plugins/learn/frontend/views/ChannelRenderer/CustomContentRenderer.vue: 11sandbox.onregistrations, a localcreateReturnMsg, andsandbox.mediator.sendMessagereplies.getContext()repliesdata: {}unconditionally (CustomContentRenderer.vue:306), and its fallback — thecontextcomputed at:83-86— isJSON.stringify(decodeURI(this.$route.query)), which yields"[object Object]".createReturnMsg(:45-59) infers success fromdatatruthiness, so a falsy-but-valid payload becomesFAILUREwitherr: null, andmediator.js:77then calls barereject().moreis documented as{cursor}butpagination.py:181-192returns the whole query-param dict, so an app receiveskind_in/descendant_of/max_resultswhile calling withkinds/descendantOf/maxResults.SandboxedContentViewerHookbuilds two bundles (buildConfig.js): the plugin'smainbundle, and asandbox_handlerbundle built with no Kolibri externals, served from the alt origin and script-loaded into the iframe. Main-side code needingContentNodeResourcecannot ship in the latter.useSandboxreturns the client as ashallowRef, re-exposed bySandboxedContentViewer, so a host can bind handlers to it.The Change
window.kolibribecomes a thin pass-through to the content API, installed only for HTML5 zip content:listresolves to{ more, results, labels }and the next page islist(more). Queries are not confined to the calling node's channel.selfshould be accepted anywhere a node id is — theidargument, or a param value such asparentordescendant_of— resolving to the calling node.Errorinstances carrying the responsestatus: propagated from the endpoint where there is one,400where the shim rejects a call before it leaves,501where the host binds no handler,504on a 30 second deadline. Destroying the shim rejects everything still outstanding.Html5ZipHandlerbesideSCORMShim, with its main half in thehtml5_viewerplugin'smainbundle. The halves should talk on the shim's ownkolibrinamespace, which needssendMessageAwaitReplyonSandboxShimand aMainClientsurface for attaching a main half, so neither the plugin nor the host reaches into the mediator.CustomContentRenderershould become a thin wrapper that renders the viewer and bindsnavigateTo.How to Get There
TopicsPagerendersCustomContentRendererwhenplugin_data.enableCustomChannelNavis set and the topic hasoptions.modality === 'CUSTOM_NAVIGATION'(TopicsPage/index.vue:709-718).window.kolibrifrom inside the sandbox iframe.Acceptance Criteria
window.kolibriexposesversion,contentNode.list,contentNode.retrieve,contentNode.treeandnavigateTo, and nothing else.list(more)returns the next page.selfresolves to the calling node both as anidargument and as a param value such asparentordescendant_of.Errorcarryingstatus: propagated from the endpoint,400for a call rejected before it leaves,501where the host binds no handler,504after a 30 second deadline. Destroying the shim rejects outstanding calls.window.kolibriis installed for HTML5 zip content only — not for H5P or Bloom.SandboxShim.sendMessageAwaitReplyand aMainClientattachment surface; neitherCustomContentRenderernor the html5_viewer main half touches a mediator directly.getRandomNodes,getChannelFilterOptions,themeRenderer,updateContextandgetContextare gone, together withfetchRandomCollection/_v2,fetchFilterOptions/_v2, the backendrandomand channelfilter_optionsactions,validateChannelTheme,createReturnMsg, and the kolibri-only events inbase.js.selfsubstitution, status mapping, deadline expiry and teardown rejection.Testing
pnpm test-jest --testPathPatterns kolibri-sandbox, plus the html5_viewer and learn plugin suites.pytest kolibri/core/content/test/for the endpoint removals.References
AI usage
Drafted with Claude Code. Every claim about current behaviour was checked against the code rather than taken on trust — line references verified, and the orphaned call chains (
random,filter_options,validateChannelTheme) confirmed by searching for callers rather than assumed. The API shape came out of a design conversation and the decisions are mine; Claude drafted the write-up, which I trimmed.