4878 (PoC)

Demo Code of solid-file-client

I’ll insert two pictures of the working demo app. For the time being it does do logins to the community-server. And this doesn’t seem to accept .acl or .meta files. But used correctly upload as well as copy do their task. After a briefing I decided to hardcode solidweb.me as Pod-Provider. For the 2.2 version I consider an options menu to choose the authentication method between Bearer Token and DPoP token.

.–.

^^^^ I gained experience and confidence doing this little job.

4871 (https://github.com/jeff-zucker/solid-file-client/tree/master/docs/examples)

upload/index.html

<!-- file docs/examples/upload/index.html -->
<!-- restructure february 2022 @ewingson -->
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
        integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>Solid Upload Demo</title>
    <!-- bundle -->
    https://cdn.jsdelivr.net/npm/@inrupt/solid-client-authn-browser@1/dist/solid-client-authn.bundle.js
	<!-- end bundle -->
</head>

<body>

    <div class="container">
        <div class="py-5 text-center">
            <h2>Solid Upload Demo</h2>
            <p class="lead">Upload files from your computer to your pod.</p>
        </div>

        <div class="row">
        			<!-- new -->
        			<!-- login area hardcoded default value, options input needs some TLC -->
                    <span id="webId"></span>
                    <input id="oidc" value="https://solidweb.me/" style="width:24em" />
                    <div class="logged-out">
                      <button id="loginButton" class="btn btn-primary">Login</button>
                    </div>
                    <div class="logged-in">
                      <button id="logoutButton" class="btn btn-secondary">Logout</button>
                    </div>
                    <!-- end new -->
            <!-- old
            <div class="col-md-4 order-md-2 mb-4">
                <h4 class="d-flex justify-content-between align-items-center mb-3">
                    <span class="text-muted">Login to your Pod</span>
                </h4>
                <div class="logged-out">
                    <button id="login" class="btn btn-primary">Login</button>
                </div>
                <div class="logged-in">
                    <p class="text-muted">Logged in as <span class="webid"></span></p>
                    <button id="logout" class="btn btn-secondary">Logout</button>
                </div>
            </div>
            end old -->
            <div class="col-md-8 order-md-1">
                <form id="upload-form" class="needs-validation">
                    <h4 class="mb-3">Settings</h4>
                    <div class="mb-3">
                        <label for="container">Folder where your files will be stored</label>
                        <div class="input-group">
                            <div class="input-group-prepend">
                                <span class="input-group-text">Folder</span>
                            </div>
                            <input type="text" class="form-control" id="container"
                                placeholder="https://solid.example.org/folder/" required>
                            <div class="invalid-feedback" style="width: 100%;">
                                Please specify where your files should be uploaded to.
                            </div>
                        </div>
                    </div>

                    <h4 class="mb-3">Files</h4>
                    <div class="mb-3">
                        <input type="file" id="files" multiple>
                    </div>
                    <hr class="mb-4">

                    <div class="not-uploading">
                        <button class="btn btn-primary btn-lg btn-block" type="submit">Start Uploading</button>
                    </div>
                    <div class="uploading">
                        <button class="btn btn-primary btn-lg btn-block disabled" type="submit">
                            Uploading...
                            <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                        </button>
                    </div>
                    <ul class="logs my-2 list-group"></ul>
                </form>
            </div>
        </div>
<br /><br />
<a href="../advanced/index.html"><button id="copy" class="btn btn-primary">to Copy</button></a>

        <footer class="my-5 pt-5 text-muted text-center text-small">
            <p class="mb-1">Implemented using solid-file-client</p>
            <ul class="list-inline">
                <li class="list-inline-item"><a href="https://github.com/jeff-zucker/solid-file-client">Source Code</a></li>
                <li class="list-inline-item"><a href="https://jeff-zucker.github.io/solid-file-client/">Docs</a></li>
                <li class="list-inline-item"><a href="https://github.com/jeff-zucker/solid-file-client/issues">Bugs</a></li>
            </ul>
        </footer>
    </div>
<!-- new -->
<!-- change location of jq -->
https://code.jquery.com/jquery-3.3.1.slim.min.js           
<script>
//get session
const iscan = solidClientAuthentication;
var session = iscan.getDefaultSession();

//define buttons
const loginButton = document.querySelector("#loginButton");
const logoutButton = document.querySelector("#logoutButton");
const webIdArea = document.querySelector("#webId");
//event
loginButton.onclick = ()=> { 
  return iscan.login({
    oidcIssuer: document.getElementById("oidc").value,
    redirectUrl: window.location.href,
    clientName: "solid-file-client-demo"
  });
};
//event
logoutButton.onclick = async ()=> { 
  await session.logout();
  showLoginStatus();
};
//handle redirect
async function handleRedirectAfterLogin() {
  await iscan.handleIncomingRedirect();
  showLoginStatus();
}
//show status
function showLoginStatus() {
  session = iscan.getDefaultSession();
  if (session.info.isLoggedIn) {
    $('.logged-in').show()
    $('.logged-out').hide()
    //loginButton.style.display = "none";
    //logoutButton.style.display = "inline-block";
    webId.innerHTML = `Logged in as ${session.info.webId}`;
  }
  else {
    $('.logged-in').hide()
    $('.logged-out').show()
    //loginButton.style.display =  "inline-block";
    //logoutButton.style.display = "none";
    webId.innerHTML = `Not logged in.`;
  }
}
//initial call
handleRedirectAfterLogin();
</script> 
<!--end new-->
    <!-- Scripts for the layout -->
    https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js
    https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js

    <!-- Solid Upload -->
    https://cdn.jsdelivr.net/npm/solid-auth-client@2.4.1/dist-lib/solid-auth-client.bundle.js
    https://cdn.jsdelivr.net/npm/solid-file-client
    http://./upload.js
</body>

</html>

upload/upload.js

// file docs/examples/upload/upload.js
// modified (co-authored) 2022/january by @ewingson in order to switch to new auth (DPoP)
// original authors @Otto-AA, @bourgeoa and @jeff-zucker
//
//new
//declare
const auth = solidClientAuthentication.getDefaultSession();      
const fileClient = new SolidFileClient( auth, { enableLogging: true })

//show / hide respective buttons
//session = iscan.getDefaultSession();
  if (session.info.isLoggedIn) {
    $('.logged-in').show();
    $('.logged-out').hide();
    webId.innerHTML = `Logged in as ${session.info.webId}`;
  }
  else {
    $('.logged-in').hide();
    $('.logged-out').show();
    webId.innerHTML = `Not logged in.`;
  }
//end new
/*old
document.getElementById('login').addEventListener('click', e => solid.auth.popupLogin({ popupUri: 'https://solidcommunity.net/common/popup.html' }))
document.getElementById('logout').addEventListener('click', e => solid.auth.logout())
solid.auth.trackSession(session => {
    if (!session) {
        $('.logged-in').hide()
        $('.logged-out').show()
    } else {
        $('.logged-in').show()
        $('.logged-out').hide()
        $('.webid').text(session.webId)
    }
})
end old*/
const setUploadStatus = isUploading => {
    if (isUploading) {
        $('.not-uploading').hide()
        $('.uploading').show()
    } else {
        $('.not-uploading').show()
        $('.uploading').hide()
    }
}
setUploadStatus(false)

const setLogStatus = showLogs => {
    if (showLogs) {
        $('.logs').show()
    } else {
        $('.logs').hide()
    }
}
const resetLogs = () => {
    setLogStatus(false)
    $('.logs').empty()
}
const addSuccessLog = msg => $('.logs').append(`<li class="list-group-item list-group-item-success">${msg}</li>`)
const addErrorLog = msg => $('.logs').append(`<li class="list-group-item list-group-item-danger">${msg}</li>`)
resetLogs()

const containerInput = document.getElementById('container')
const filesInput = document.getElementById('files')

document.getElementById('upload-form').addEventListener('submit', async e => {
    e.preventDefault()
    const parentContainer = containerInput.value + ((containerInput.value.endsWith('/')) ? '' : '/')
    const files = filesInput.files

    console.log(`Uploading ${files.length} file(s) to ${parentContainer}`)

    setUploadStatus(true)
    resetLogs()
    setLogStatus(true)
    for (let i = 0; i < files.length; i++) {
        const file = files[i]
        const url = parentContainer + file.name

        console.log(`Uploading ${file.name} to ${url}`)
        try {
            // Uploading the file
            // Content can be a file from a html input
            // or a string. For json objects, use JSON.stringify(object)
            const res = await fileClient.putFile(url, file, file.type)
            const msg = `${res.status} Uploaded ${file.name} to ${res.url}`
            console.log(msg)
            addSuccessLog(msg)
        } catch (err) {
            console.error(err)
            addErrorLog(err.message)
        }
    }
    setUploadStatus(false)
})

advanced/index.html

<!-- file docs/examples/advanced/index.html -->
<!-- restructure february 2022 @ewingson -->
<!-- new index -->
<!doctype html>
<html>
<head>
<!-- required meta tags -->
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- bootstrap css -->
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<!-- bundle -->
	https://cdn.jsdelivr.net/npm/@inrupt/solid-client-authn-browser@1/dist/solid-client-authn.bundle.js
<!-- optional?, jquery -->
	https://code.jquery.com/jquery-3.3.1.slim.min.js
<!-- optional?, popper -->
	https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js
<!-- optional?, stackpath -->
	https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js
<!-- source -->
	https://cdn.jsdelivr.net/npm/solid-file-client
	<title>Solid Copy Demo</title>
<style>
body {
    padding:0;
    margin:0;
    overflow:hidden;
}
#check {
    width : 25vw;
    height : 100vh;
    padding:5vh;
    background-color:#e4e4e4;
    position:absolute;
}
#check h3 {
    text-align:center;
    width:100%;
    margin-bottom:1em;
}
#check button {
    width:100%;
    margin-bottom:1em;
    font-size : large;
}
#display {
    border:0;
    left : 25vw;
    width : 75vw;
    height : 99vh;
    position:absolute;
}
#note {
    padding:1em;
    border:1px solid black;
}
</style>
</head>

<body>
<div class="container">
		<div class="py-5 text-center">
            <h2>Solid Copy Demo</h2>
            <p class="lead">Copy folders or files within your solid pod or from a public source to your pod.</p>
        </div>
	<div class="row">
		<!-- auth status -->
		<span id="webId"></span>
        	<input id="oidc" value="https://solidweb.me/" style="width:24em" />
        <div class="logged-out">
        	<button id="loginButton" class="btn btn-primary">Login</button>
        </div>
        <div class="logged-in">
        	<button id="logoutButton" class="btn btn-secondary">Logout</button>
        </div>

       <!-- the formular input happens here -->
       <br />
       <div><!-- form div -->

       <form id="copy-form" class="needs-validation">
                    <div class="mb-3">
                        <label for="src">Folder/File from your pod or public pod</label>
                        <div class="input-group">
                            <div class="input-group-prepend">
                                <span class="input-group-text">From</span>
                            </div>
                            <input type="text" class="form-control" id="src"
                                placeholder="https://solid.example.org/folder/" required>
                            <div class="invalid-feedback" style="width: 100%;">
                                Please specify which item you want to copy.
                            </div>
                        </div>
                    </div>
                    <div class="mb-3">
                        <label for="dest">Destination at your pod</label>
                        <div class="input-group">
                            <div class="input-group-prepend">
                                <span class="input-group-text">To</span>
                            </div>
                            <input type="text" class="form-control" id="dest"
                                placeholder="https://solid.example.org/other/" required>
                            <div class="invalid-feedback" style="width: 100%;">
                                Please specify where it should be copied to.
                            </div>
                        </div>
                    </div>

        <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="merge-option" id="merge-replace" value="replace" checked>
                        <label class="form-check-label" for="merge-replace">Replace target</label>
                      </div>
                      <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="merge-option" id="merge-keep-source" value="keep-source">
                        <label class="form-check-label" for="merge-keep-source">Merge and keep source</label>
                      </div>
                      <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="merge-option" id="merge-keep-target" value="keep-target">
                        <label class="form-check-label" for="merge-keep-target">Merge and keep target</label>
                      </div>
                    <div class="custom-control custom-checkbox">
                        <input type="checkbox" class="custom-control-input" id="create-path" checked>
                        <label class="custom-control-label" for="create-path">Create path to target container</label>
                    </div>
                    <div class="custom-control custom-checkbox">
                        <input type="checkbox" class="custom-control-input" id="with-acl" checked>
                        <label class="custom-control-label" for="with-acl">Include acl files</label>
                    </div>
                    <div class="custom-control custom-checkbox">
                        <input type="checkbox" class="custom-control-input" id="with-meta" checked>
                        <label class="custom-control-label" for="with-meta">Include meta files</label>
                    </div>
                    <hr class="mb-4">
                    <div class="not-copying">
                        <button class="btn btn-primary btn-lg btn-block" type="submit">Start Copying</button>
                    </div>
                    <div class="copying">
                        <button class="btn btn-primary btn-lg btn-block disabled" type="submit">
                            Copying...
                            <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                        </button>
                    </div>
										<!-- SuccessLog / ErrorLog output -->
                    <ul class="logs my-2 list-group"></ul>
                </form>
       </div><!-- end form div -->

	</div>
	<br /><br />
<a href="../upload/index.html"><button id="upload" class="btn btn-primary">to Upload</button></a>
		<footer class="my-5 pt-5 text-muted text-center text-small">
            <p class="mb-1">Implemented using solid-file-client</p>
            <ul class="list-inline">
                <li class="list-inline-item"><a href="https://github.com/jeff-zucker/solid-file-client">Source Code</a></li>
                <li class="list-inline-item"><a href="https://jeff-zucker.github.io/solid-file-client/">Docs</a></li>
                <li class="list-inline-item"><a href="https://github.com/jeff-zucker/solid-file-client/issues">Bugs</a></li>
            </ul>
        </footer>

</div>

<script>
//get session
const iscan = solidClientAuthentication;
console.log('iscan initialized');
// I have let the auth stuff in index.html and put the copy code in an extra file. now I've made it verbose and try to follow the flow.
var session = iscan.getDefaultSession();
console.log('define session / iscan.getDefaultSession done')

//define buttons
console.log('define Buttons and WebID');
const loginButton = document.querySelector("#loginButton");
const logoutButton = document.querySelector("#logoutButton");
const webIdArea = document.querySelector("#webId");
console.log('buttons and WebID defined');
//event
loginButton.onclick = ()=> {
	console.log('login button clicked');
	return iscan.login({
    	oidcIssuer: document.getElementById("oidc").value,
    	redirectUrl: window.location.href,
    	clientName: "solid-file-client-demo"
  	});
};
//event
logoutButton.onclick = async ()=> {
	console.log('logout button clicked');
	await session.logout();
	console.log('logout done');
	showLoginStatus();
};
//handle redirect
async function handleRedirectAfterLogin() {
	console.log('handle redirect called');
	await iscan.handleIncomingRedirect();
	showLoginStatus();
	//if (session.info.isLoggedIn)  main(session); //call main function
}
//show status
function showLoginStatus() {
	console.log('show status start');
	session = iscan.getDefaultSession();
	if (session.info.isLoggedIn) {
		$('.logged-in').show();
		$('.logged-out').hide();
		webId.innerHTML = `Logged in as ${session.info.webId}`;
		console.log('show logged in');
	}
	else {
		$('.logged-in').hide();
		$('.logged-out').show();
		webId.innerHTML = `Not logged in.`;
		console.log('show logged out');
	}
}
//initial call
console.log('call handleRedirectAfterLogin()')
handleRedirectAfterLogin();
</script>
<!-- copyscript -->
http://./solidCopyDemo.js
</body>
</html>

advanced/solidCopyDemo.js

// file docs/examples/advanced/solidCopyDemo.js
// restructure february 2022 @ewingson
//
//code goes here
//trying to understand the logic and put everything in the right order / verbose
//this is the codebase that is called at the bottom of advanced/index.html
//import helper class
import solidCopyDemoHelper from '../tools/solidCopyDemoHelper.js';
console.log('file solidCopyDemo import done');
console.log('solidCopyDemo.js code execution');
const auth = solidClientAuthentication.getDefaultSession();
const fileClient = new SolidFileClient( auth, { enableLogging: true });
console.log('SolidFileClient object initialized');
//this seems to be the actual copy command
const { MERGE: { REPLACE, KEEP_SOURCE, KEEP_TARGET } } = SolidFileClient;
console.log('MERGE const');

const setCopyStatus = isCopying => {
            if (isCopying) {
                $('.not-copying').hide()
                $('.copying').show()
                console.log('show copying');
            } else {
                $('.not-copying').show()
                $('.copying').hide()
                console.log('show not copying');
            }
        }
        setCopyStatus(false)

        const setLogStatus = showLogs => {
            if (showLogs) {
                $('.logs').show()
                console.log('show logs');
            } else {
                $('.logs').hide()
                console.log('hide logs');
            }
        }
        const resetLogs = () => {
          console.log('reset logs');
            setLogStatus(false)
            $('.logs').empty()
            console.log('logs resetted');
        }

        console.log('codeblock main // MOVED');
        resetLogs()

        console.log('process form');
                document.getElementById('copy-form').addEventListener('submit', async e => {
                    e.preventDefault()
                    console.log('submit event done');

                    console.log('get elements by id');
                	const fromInput = document.getElementById('src')
               		const destInput = document.getElementById('dest')
                	const createPathInput = document.getElementById('create-path')
                	const withAclInput = document.getElementById('with-acl')
                	const withMetaInput = document.getElementById('with-meta')
                	const getMergeInput = () => document.querySelector('input[name="merge-option"]:checked')

                    const from = fromInput.value
                    const to = destInput.value
                    const createPath = createPathInput.checked
                    const withMeta = withMetaInput.checked
                    const withAcl = withAclInput.checked

                    const mergeVal = getMergeInput().value
                    let merge = REPLACE
                    if (mergeVal === 'keep-source')
                        merge = KEEP_SOURCE
                    else if (mergeVal === 'keep-target')
                        merge = KEEP_TARGET

                    console.log(from + '_' + to + '_' + merge + '_' + createPath + '_' + withAcl + '_' + withMeta);
                    let help = solidCopyDemoHelper(from, to, merge, createPath, withAcl, withMeta);
                    console.log(help);

                    console.log('set response');
                    //wo kommen diese 3 attribute/eigenschaften her ?
                    const responseToMsg = response => `${response.status} ${response.statusText} ${response.url}`

                    try {
                      console.log('set copy status true');
                        setCopyStatus(true)
                        resetLogs()

                        // Copy a file or folder
                        console.log('actual copy work');

                        const res = await fileClient.copy(from, to, {
                            merge,
                            createPath,
                            withAcl,
                            withMeta
                        })

                        res.forEach(response => {
                            const msg = responseToMsg(response)
                            console.log(msg)
                            const addSuccessLog = msg => $('.logs').append(`<li class="list-group-item list-group-item-success">${msg}</li>`)

                            addSuccessLog(msg)
                        })
                    } catch (err) {
                        err.rejectedErrors.forEach(err => {
                            console.error(err)
                            const addErrorLog = msg => $('.logs').append(`<li class="list-group-item list-group-item-danger">${msg}</li>`)

                            addErrorLog(err.message)
                        })
                    }
                    finally {
                      console.log('try/catch passed.')
                    }
                    console.log('set copy status false');
                    setCopyStatus(false)
                    console.log('set log status true');
                    setLogStatus(true)
                })

tools/solidCopyDemoHelper.js

// file docs/examples/tools/solidCopyDemoHelper.js
// restructure february 2022 by @ewingson
// helper class that fulfills the needed promise in solidCopyDemo.js
async function solidCopyDemoHelper(von,an,mer,cp,wacl,wmeta) {
console.log('solidCopyDemoHelper code started');
let antwort = 42;
//const authn = solidClientAuthentication.getDefaultSession();
//const fc = new SolidFileClient( authn, { enableLogging: true });

/*let content = fc.copy(von, an, {
    mer,
    cp,
    wacl,
    wmeta
})*/

return antwort;
}
export default solidCopyDemoHelper;

4724 (https://solidweb.me/.well-known/openid-configuration)

	
authorization_endpoint	"https://solidweb.me/idp/auth"
claims_parameter_supported	true
claims_supported	
0	"webid"
1	"client_id"
2	"sub"
3	"sid"
4	"auth_time"
5	"iss"
code_challenge_methods_supported	
0	"S256"
end_session_endpoint	"https://solidweb.me/idp/session/end"
grant_types_supported	
0	"implicit"
1	"authorization_code"
2	"refresh_token"
id_token_signing_alg_values_supported	
0	"HS256"
1	"RS256"
issuer	"https://solidweb.me/"
jwks_uri	"https://solidweb.me/idp/jwks"
registration_endpoint	"https://solidweb.me/idp/reg"
response_modes_supported	
0	"form_post"
1	"fragment"
2	"query"
response_types_supported	
0	"code id_token"
1	"code"
2	"id_token"
3	"none"
scopes_supported	
0	"openid"
1	"profile"
2	"offline_access"
subject_types_supported	
0	"public"
1	"pairwise"
token_endpoint_auth_methods_supported	
0	"none"
1	"client_secret_basic"
2	"client_secret_jwt"
3	"client_secret_post"
4	"private_key_jwt"
token_endpoint_auth_signing_alg_values_supported	
0	"HS256"
1	"RS256"
2	"PS256"
3	"ES256"
4	"EdDSA"
token_endpoint	"https://solidweb.me/idp/token"
request_object_signing_alg_values_supported	
0	"HS256"
1	"RS256"
2	"PS256"
3	"ES256"
4	"EdDSA"
request_parameter_supported	false
request_uri_parameter_supported	true
require_request_uri_registration	true
userinfo_endpoint	"https://solidweb.me/idp/me"
userinfo_signing_alg_values_supported	
0	"HS256"
1	"RS256"
introspection_endpoint	"https://solidweb.me/idp/token/introspection"
introspection_endpoint_auth_methods_supported	
0	"none"
1	"client_secret_basic"
2	"client_secret_jwt"
3	"client_secret_post"
4	"private_key_jwt"
introspection_endpoint_auth_signing_alg_values_supported	
0	"HS256"
1	"RS256"
2	"PS256"
3	"ES256"
4	"EdDSA"
dpop_signing_alg_values_supported	
0	"RS256"
1	"PS256"
2	"ES256"
3	"EdDSA"
revocation_endpoint	"https://solidweb.me/idp/token/revocation"
revocation_endpoint_auth_methods_supported	
0	"none"
1	"client_secret_basic"
2	"client_secret_jwt"
3	"client_secret_post"
4	"private_key_jwt"
revocation_endpoint_auth_signing_alg_values_supported	
0	"HS256"
1	"RS256"
2	"PS256"
3	"ES256"
4	"EdDSA"
claim_types_supported	
0	"normal"
solid_oidc_supported	"https://solidproject.org/TR/solid-oidc"