Skip to content

Conversation

@berezovskyi
Copy link
Member

@berezovskyi berezovskyi commented Jan 18, 2025

I spent 3 hours updating Angular 10 to 11 yesterday and the migration is not fully complete. Mind you, the current version of Angular is 19 and the recommendation is to upgrade only one major version at a time.

Now I spent just 2 hours to write a complete delegated dialog implementation using HTMX instead of making AJAX requests and using Angular to dynamically render results.

Instead, HTMX is a hypermedia framework that is declaratively configured to make HTTP requests and place responses into the HTML DOM. OSLC, being an LDP REST API built with hypermedia principles at heart is perfectly aligned with HTMX. A lot of code can be removed as a result.

One of the best things is that HTMX does not intend to make breaking change like modern frontend frameworks and be more like jQuery in terms of stability. I think it's a perfect fit for the refimpl that needs to reduce the maintenance burden.

Screen.Recording.2025-01-18.at.14.23.29.mov

Comment on lines +22 to +34
<aside>
<nav>
<ul>
<c:forEach var="requirement" items="${resources}">
<li>
<a href="${requirement.about}"
onclick="sendOslcSelectionPostMessage(this, event)"
>${requirement.title}</a>
</li>
</c:forEach>
</ul>
</nav>
</aside>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this tiny bit of HTML is what gets rendered every time HTMX makes a search request and it dynamically replaces the contents of the #search-results div with this content returned by the server. Simple and easy.

@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from 7f598be to b3d8e02 Compare January 23, 2025 07:41
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from 513467a to d3a4031 Compare February 22, 2025 07:41
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from b25d924 to 4cb0c01 Compare March 5, 2025 07:42
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from ab8d340 to b8d6411 Compare April 15, 2025 07:43
@github-actions github-actions bot force-pushed the b-htmx branch 3 times, most recently from 72e2710 to 707ebc9 Compare May 16, 2025 07:42
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from 254d884 to fc40afc Compare June 28, 2025 07:42
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from 024f893 to 74ba99c Compare July 6, 2025 07:42
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from 66bcc5a to b610c33 Compare July 13, 2025 07:44
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from b91907a to d1c1216 Compare July 20, 2025 07:44
@github-actions github-actions bot force-pushed the b-htmx branch 3 times, most recently from 3485858 to 44a1d94 Compare August 10, 2025 07:43
@github-actions github-actions bot force-pushed the b-htmx branch 3 times, most recently from 59b167c to d1bb9b2 Compare November 29, 2025 07:42
@github-actions github-actions bot force-pushed the b-htmx branch 2 times, most recently from d90a54d to 597ab05 Compare December 6, 2025 07:41
Copilot AI review requested due to automatic review settings December 6, 2025 07:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.

Comments suppressed due to low confidence (2)

src/server-rm/src/main/webapp/co/oslc/refimpl/rm/gen/requirementselector-results.jsp:29

  • The requirement resource title is rendered directly into the HTML without escaping. If a requirement title contains HTML special characters or malicious script tags, this could lead to XSS vulnerabilities. Use JSTL's c:out tag with escapeXml="true" or fn:escapeXml() to properly escape the title content.
          >${requirement.title}</a>

src/server-rm/src/main/webapp/co/oslc/refimpl/rm/gen/requirementselector-results.jsp:27

  • The requirement.about URI is rendered directly in the href attribute without escaping. If the URI contains quotes or other special characters, it could break the attribute or lead to attribute injection attacks. Use JSTL c:out or fn:escapeXml() to properly escape the URI.
        <a href="${requirement.about}"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
]
}
window.parent.postMessage("oslc-response:" + JSON.stringify(message), '*')
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wildcard origin '*' in postMessage allows any website to receive the OSLC selection response, which could expose sensitive resource information to malicious sites. Consider using the opener's origin or a configured whitelist of allowed origins instead.

Suggested change
window.parent.postMessage("oslc-response:" + JSON.stringify(message), '*')
// Extract parent origin from document.referrer
let parentOrigin = null;
try {
if (document.referrer) {
parentOrigin = new URL(document.referrer).origin;
}
} catch (e) {
// If referrer is malformed, do not send the message
parentOrigin = null;
}
if (parentOrigin) {
window.parent.postMessage("oslc-response:" + JSON.stringify(message), parentOrigin);
} else {
console.error("Unable to determine parent origin. OSLC selection response not sent.");
}

Copilot uses AI. Check for mistakes.

<p id="loadingMessage" style="display: none;">Pondering your search. Please stand by ...</p>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SRI hash is for HTMX version 2.0.4, but there's no corresponding integrity check for the PicoCSS CDN link. While the PicoCSS link uses HTTPS, adding an integrity attribute would provide additional protection against CDN compromise.

Suggested change
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css" integrity="sha384-+1Qw6QwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQwQw==" crossorigin="anonymous">

Copilot uses AI. Check for mistakes.
Comment on lines 300 to +313
log.error("A empty search should return an empty list and not NULL!");
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);

return Response.noContent().build();
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the delegate returns null for resources, the code logs an error stating "A empty search should return an empty list and not NULL!" and then returns a 204 No Content response. However, this masks a potential bug in the delegate implementation rather than handling it appropriately. Consider throwing an exception or returning a 500 Internal Server Error to signal that the delegate violated its contract.

Copilot uses AI. Check for mistakes.
RequestDispatcher rd = httpServletRequest.getRequestDispatcher("/co/oslc/refimpl/rm" +
"/gen/requirementselector-results.jsp");
httpServletRequest.setAttribute("resources", resources);
rd.forward(httpServletRequest, httpServletResponse);
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When forwarding the request to a JSP that returns HTML fragments for HTMX (lines 289-292), the method continues execution after rd.forward() without returning. This means the subsequent code block (lines 310-313) will still be evaluated, potentially causing issues. Add a return statement after the forward() call to prevent fall-through.

Suggested change
rd.forward(httpServletRequest, httpServletResponse);
rd.forward(httpServletRequest, httpServletResponse);
return null;

Copilot uses AI. Check for mistakes.
final List<Requirement> resources = delegate.RequirementSelector(httpServletRequest,
serviceProviderId, terms);
if (resources != null) {
if ("true".equalsIgnoreCase(httpServletRequest.getHeader("hx-request"))) {
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTMX request detection using a custom header "hx-request" should be case-sensitive checked. The equalsIgnoreCase method could match "TRUE", "True", etc., but HTMX sends lowercase "true". While this is more lenient (which may be desirable), it's worth noting that the official HTMX header value is always lowercase "true".

Suggested change
if ("true".equalsIgnoreCase(httpServletRequest.getHeader("hx-request"))) {
if ("true".equals(httpServletRequest.getHeader("hx-request"))) {

Copilot uses AI. Check for mistakes.
<button type="button" onclick="search( '<%= selectionUri %>' )">Search</button>
<input type="search" id="searchTerms" name="terms" placeholder="Begin typing to search"
autofocus autocomplete="off"
hx-trigger="input changed delay:120ms, keyup[key=='Enter'], load"
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTMX trigger configuration 'input changed delay:120ms, keyup[key=='Enter'], load' includes a 'load' event which will fire when the page loads, even when the search input is empty. This will cause an unnecessary request to the server on page load. Consider removing 'load' from the trigger if you don't want to perform a search with empty terms on initial page load.

Suggested change
hx-trigger="input changed delay:120ms, keyup[key=='Enter'], load"
hx-trigger="input changed delay:120ms, keyup[key=='Enter']"

Copilot uses AI. Check for mistakes.
Comment on lines +294 to +295
// this is really bad, we should not be mixing HTML and JSON without content
// negotiation
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on lines 294-295 acknowledges that mixing HTML and JSON responses without proper content negotiation is "really bad", but this approach is still implemented. This creates technical debt and violates REST API principles. Consider refactoring to use proper Accept header negotiation or creating separate endpoints for HTML and JSON responses.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +75
onEvent : function(name, evt) {
var elt = evt.detail.elt;
if(name === "htmx:beforeRequest") {
elt.setAttribute("aria-busy", "true")
} else if(name === "htmx:afterRequest" ) {
elt.removeAttribute("aria-busy")
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JavaScript variables 'elt', 'name', and 'evt' use inconsistent naming conventions. The code uses 'elt' (abbreviated) alongside 'name' and 'evt' (also abbreviated). Consider using more descriptive names like 'element', 'eventName', and 'event' for better code clarity, or at least be consistent with abbreviation style.

Suggested change
onEvent : function(name, evt) {
var elt = evt.detail.elt;
if(name === "htmx:beforeRequest") {
elt.setAttribute("aria-busy", "true")
} else if(name === "htmx:afterRequest" ) {
elt.removeAttribute("aria-busy")
onEvent : function(eventName, event) {
var element = event.detail.elt;
if(eventName === "htmx:beforeRequest") {
element.setAttribute("aria-busy", "true")
} else if(eventName === "htmx:afterRequest" ) {
element.removeAttribute("aria-busy")

Copilot uses AI. Check for mistakes.
// we need to return HTML fragment
RequestDispatcher rd = httpServletRequest.getRequestDispatcher("/co/oslc/refimpl/rm" +
"/gen/requirementselector-results.jsp");
httpServletRequest.setAttribute("resources", resources);
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's inconsistent indentation on line 291 where httpServletRequest.setAttribute has extra leading whitespace compared to the surrounding lines. This creates visual inconsistency in the code block.

Suggested change
httpServletRequest.setAttribute("resources", resources);
httpServletRequest.setAttribute("resources", resources);

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +29
<a href="${requirement.about}"
onclick="sendOslcSelectionPostMessage(this, event)"
>${requirement.title}</a>
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requirement.title and requirement.about fields are rendered directly into HTML without any escaping, while Requirement instances are populated from unsanitized client input in RestDelegate.createRequirement. A malicious client can create a requirement whose title contains HTML/JavaScript so that when this selection dialog is opened, the injected script executes in the browser (stored XSS) and can act with the privileges of the RM provider UI. Ensure that user-controlled fields are properly encoded for HTML text and attribute contexts before rendering (for example, by using JSP/JSTL escaping utilities) so that they cannot inject markup or script.

Suggested change
<a href="${requirement.about}"
onclick="sendOslcSelectionPostMessage(this, event)"
>${requirement.title}</a>
<a href="<c:out value='${requirement.about}'/>"
onclick="sendOslcSelectionPostMessage(this, event)"
><c:out value="${requirement.title}"/></a>

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings December 21, 2025 07:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Signed-off-by: Andrew Berezovskyi <andriib@kth.se>
Signed-off-by: Andrew Berezovskyi <andriib@kth.se>
Signed-off-by: Andrew Berezovskyi <andriib@kth.se>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants